SPV: When passing structs of opaque types, flatten and pass the members instead.

This avoids either A) needing uniformConstant struct, or
B) initializing a struct with opaque members, as writing them is not
allowed.
This commit is contained in:
John Kessenich
2017-05-26 00:01:36 -06:00
parent d66c5b1299
commit 750c2d07f7
6 changed files with 528 additions and 70 deletions

View File

@@ -774,7 +774,7 @@ TIntermTyped* HlslParseContext::handleBracketDereference(const TSourceLoc& loc,
else {
// at least one of base and index is variable...
if (base->getAsSymbolNode() && (wasFlattened(base) || shouldFlattenUniform(base->getType()))) {
if (base->getAsSymbolNode() && (wasFlattened(base) || shouldFlatten(base->getType()))) {
if (index->getQualifier().storage != EvqConst)
error(loc, "Invalid variable index to flattened array", base->getAsSymbolNode()->getName().c_str(), "");
@@ -981,7 +981,7 @@ TIntermTyped* HlslParseContext::handleDotDereference(const TSourceLoc& loc, TInt
}
}
if (fieldFound) {
if (base->getAsSymbolNode() && (wasFlattened(base) || shouldFlattenUniform(base->getType()))) {
if (base->getAsSymbolNode() && (wasFlattened(base) || shouldFlatten(base->getType()))) {
result = flattenAccess(base, member);
} else {
// Update the base and member to access if this was a split structure.
@@ -1115,14 +1115,13 @@ TType& HlslParseContext::split(TType& type, TString name, const TType* outerStru
return type;
}
// Is this a uniform array which should be flattened?
bool HlslParseContext::shouldFlattenUniform(const TType& type) const
// Is this a uniform array or structure which should be flattened?
bool HlslParseContext::shouldFlatten(const TType& type) const
{
const TStorageQualifier qualifier = type.getQualifier().storage;
return qualifier == EvqUniform &&
((type.isArray() && intermediate.getFlattenUniformArrays()) || type.isStruct()) &&
type.containsOpaque();
return (qualifier == EvqUniform && type.isArray() && intermediate.getFlattenUniformArrays()) ||
type.isStruct() && type.containsOpaque();
}
// Top level variable flattening: construct data
@@ -1285,16 +1284,22 @@ bool HlslParseContext::wasSplit(const TIntermTyped* node) const
// 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.
// Also assumes that initFlattening() and finalizeFlattening() bracket usage.
TIntermTyped* HlslParseContext::flattenAccess(TIntermTyped* base, int member)
{
const TType dereferencedType(base->getType(), member); // dereferenced type
const TIntermSymbol& symbolNode = *base->getAsSymbolNode();
const auto flattenData = flattenMap.find(symbolNode.getId());
TIntermTyped* flattened = flattenAccess(symbolNode.getId(), member, dereferencedType);
return flattened ? flattened : base;
}
TIntermTyped* HlslParseContext::flattenAccess(int uniqueId, int member, const TType& dereferencedType)
{
const auto flattenData = flattenMap.find(uniqueId);
if (flattenData == flattenMap.end())
return base;
return nullptr;
// Calculate new cumulative offset from the packed tree
flattenOffset.back() = flattenData->second.offsets[flattenOffset.back() + member];
@@ -1307,7 +1312,7 @@ TIntermTyped* HlslParseContext::flattenAccess(TIntermTyped* base, int member)
} else {
// If this is not the final flattening, accumulate the position and return
// an object of the partially dereferenced type.
return new TIntermSymbol(symbolNode.getId(), "flattenShadow", dereferencedType);
return new TIntermSymbol(uniqueId, "flattenShadow", dereferencedType);
}
}
@@ -1663,15 +1668,32 @@ TIntermAggregate* HlslParseContext::handleFunctionDefinition(const TSourceLoc& l
symbolTable.makeInternalVariable(*variable);
pushImplicitThis(variable);
}
// Insert the parameters with name in the symbol table.
if (! symbolTable.insert(*variable))
error(loc, "redefinition", variable->getName().c_str(), "");
// Add the parameter to the AST
paramNodes = intermediate.growAggregate(paramNodes,
intermediate.addSymbol(*variable, loc),
loc);
// Add hidden parameter for struct buffer counters, if needed.
// Add parameters to the AST list.
if (shouldFlatten(variable->getType())) {
// Expand the AST parameter nodes (but not the name mangling or symbol table view)
// for structures that need to be flattened.
flatten(loc, *variable);
const TTypeList* structure = variable->getType().getStruct();
for (int mem = 0; mem < (int)structure->size(); ++mem) {
initFlattening();
paramNodes = intermediate.growAggregate(paramNodes,
flattenAccess(variable->getUniqueId(), mem, *(*structure)[mem].type),
loc);
finalizeFlattening();
}
} else {
// Add the parameter to the AST
paramNodes = intermediate.growAggregate(paramNodes,
intermediate.addSymbol(*variable, loc),
loc);
}
// Add hidden AST parameter for struct buffer counters, if needed.
addStructBufferHiddenCounterParam(loc, param, paramNodes);
} else
paramNodes = intermediate.growAggregate(paramNodes, intermediate.addSymbol(*param.type, loc), loc);
@@ -2265,7 +2287,7 @@ TIntermTyped* HlslParseContext::handleAssign(const TSourceLoc& loc, TOperator op
const bool flattened = isLeft ? isFlattenLeft : isFlattenRight;
const bool split = isLeft ? isSplitLeft : isSplitRight;
const TIntermTyped* outer = isLeft ? outerLeft : outerRight;
const TVector<TVariable*>& flatVariables = isLeft ? *leftVariables : *rightVariables;
const TVector<TVariable*>& flatVariables = isLeft ? *leftVariables : *rightVariables;
// Index operator if it's an aggregate, else EOpNull
const TOperator op = node->getType().isArray() ? EOpIndexDirect :
@@ -2320,7 +2342,7 @@ TIntermTyped* HlslParseContext::handleAssign(const TSourceLoc& loc, TOperator op
const int elementsToCopy = std::min(elementsL, elementsR);
// array case
for (int element=0; element < elementsToCopy; ++element) {
for (int element = 0; element < elementsToCopy; ++element) {
arrayElement.push_back(element);
// Add a new AST symbol node if we have a temp variable holding a complex RHS.
@@ -2511,7 +2533,7 @@ bool HlslParseContext::hasStructBuffCounter(const TType& type) const
case EbvRWStructuredBuffer: // ...
return true;
default:
return false; // the other structuredbfufer types do not have a counter.
return false; // the other structuredbuffer types do not have a counter.
}
}
@@ -4419,14 +4441,18 @@ TIntermTyped* HlslParseContext::handleFunctionCall(const TSourceLoc& loc, TFunct
pushFrontArguments(intermediate.addSymbol(*getImplicitThis(thisDepth)), arguments);
}
// Convert 'in' arguments
// Convert 'in' arguments, so that types match.
// However, skip those that need expansion, that is covered next.
if (arguments)
addInputArgumentConversions(*fnCandidate, arguments);
// If any argument is a pass-by-reference struct buffer with an associated counter
// buffer, we have to add another hidden parameter for that counter.
if (aggregate && !builtIn)
addStructBuffArguments(loc, aggregate);
// Expand arguments. Some arguments must physically expand to a different set
// than what the shader declared and passes.
if (arguments && !builtIn)
expandArguments(loc, *fnCandidate, arguments);
// Expansion may have changed the form of arguments
aggregate = arguments ? arguments->getAsAggregate() : nullptr;
op = fnCandidate->getBuiltInOp();
if (builtIn && op != EOpNull) {
@@ -4464,24 +4490,35 @@ TIntermTyped* HlslParseContext::handleFunctionCall(const TSourceLoc& loc, TFunct
decomposeSampleMethods(loc, result, arguments); // HLSL->AST sample method decompositions
decomposeGeometryMethods(loc, result, arguments); // HLSL->AST geometry method decompositions
// Create the qualifier list, carried in the AST for the call.
// Because some arguments expand to multiple arguments, the qualifier list will
// be longer than the formal parameter list.
if (result == fnNode && result->getAsAggregate()) {
TQualifierList& qualifierList = result->getAsAggregate()->getQualifierList();
for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
TStorageQualifier qual = (*fnCandidate)[i].type->getQualifier().storage;
if (hasStructBuffCounter(*(*fnCandidate)[i].type)) {
// add buffer and counter buffer argument qualifier
qualifierList.push_back(qual);
qualifierList.push_back(qual);
} else if (shouldFlatten(*(*fnCandidate)[i].type)) {
// add structure member expansion
for (int memb = 0; memb < (int)(*fnCandidate)[i].type->getStruct()->size(); ++memb)
qualifierList.push_back(qual);
} else {
// Normal 1:1 case
qualifierList.push_back(qual);
}
}
}
// Convert 'out' arguments. If it was a constant folded built-in, it won't be an aggregate anymore.
// Built-ins with a single argument aren't called with an aggregate, but they also don't have an output.
// Also, build the qualifier list for user function calls, which are always called with an aggregate.
// We don't do this is if there has been a decomposition, which will have added its own conversions
// for output parameters.
if (result == fnNode && result->getAsAggregate()) {
TQualifierList& qualifierList = result->getAsAggregate()->getQualifierList();
for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
TStorageQualifier qual = (*fnCandidate)[i].type->getQualifier().storage;
qualifierList.push_back(qual);
// add counter buffer argument qualifier
if (hasStructBuffCounter(*(*fnCandidate)[i].type))
qualifierList.push_back(qual);
}
if (result == fnNode && result->getAsAggregate())
result = addOutputArgumentConversions(*fnCandidate, *result->getAsOperator());
}
}
}
@@ -4512,20 +4549,22 @@ void HlslParseContext::pushFrontArguments(TIntermTyped* front, TIntermTyped*& ar
void HlslParseContext::addInputArgumentConversions(const TFunction& function, TIntermTyped*& arguments)
{
TIntermAggregate* aggregate = arguments->getAsAggregate();
const auto setArg = [&](int argNum, TIntermTyped* arg) {
// Replace a single argument with a single argument.
const auto setArg = [&](int paramNum, TIntermTyped* arg) {
if (function.getParamCount() == 1)
arguments = arg;
else {
if (aggregate)
aggregate->getSequence()[argNum] = arg;
else
if (aggregate == nullptr)
arguments = arg;
else
aggregate->getSequence()[paramNum] = arg;
}
};
// Process each argument's conversion
for (int i = 0; i < function.getParamCount(); ++i) {
if (! function[i].type->getQualifier().isParamInput())
for (int param = 0; param < function.getParamCount(); ++param) {
if (! function[param].type->getQualifier().isParamInput())
continue;
// At this early point there is a slight ambiguity between whether an aggregate 'arguments'
@@ -4533,42 +4572,121 @@ void HlslParseContext::addInputArgumentConversions(const TFunction& function, TI
// means take 'arguments' itself as the one argument.
TIntermTyped* arg = function.getParamCount() == 1
? arguments->getAsTyped()
: (aggregate ? aggregate->getSequence()[i]->getAsTyped() : arguments->getAsTyped());
if (*function[i].type != arg->getType()) {
: (aggregate ?
aggregate->getSequence()[param]->getAsTyped() :
arguments->getAsTyped());
if (*function[param].type != arg->getType()) {
// In-qualified arguments just need an extra node added above the argument to
// convert to the correct type.
TIntermTyped* convArg = intermediate.addConversion(EOpFunctionCall, *function[i].type, arg);
TIntermTyped* convArg = intermediate.addConversion(EOpFunctionCall, *function[param].type, arg);
if (convArg != nullptr)
convArg = intermediate.addUniShapeConversion(EOpFunctionCall, *function[i].type, convArg);
convArg = intermediate.addUniShapeConversion(EOpFunctionCall, *function[param].type, convArg);
if (convArg != nullptr)
setArg(i, convArg);
setArg(param, convArg);
else
error(arg->getLoc(), "cannot convert input argument, argument", "", "%d", i);
error(arg->getLoc(), "cannot convert input argument, argument", "", "%d", param);
} else {
if (wasFlattened(arg) || wasSplit(arg)) {
// Will make a two-level subtree.
// The deepest will copy member-by-member to build the structure to pass.
// The level above that will be a two-operand EOpComma sequence that follows the copy by the
// object itself.
TVariable* internalAggregate = makeInternalVariable("aggShadow", *function[i].type);
internalAggregate->getWritableType().getQualifier().makeTemporary();
TIntermSymbol* internalSymbolNode = new TIntermSymbol(internalAggregate->getUniqueId(),
internalAggregate->getName(),
internalAggregate->getType());
internalSymbolNode->setLoc(arg->getLoc());
// This makes the deepest level, the member-wise copy
TIntermAggregate* assignAgg = handleAssign(arg->getLoc(), EOpAssign, internalSymbolNode, arg)->getAsAggregate();
// If both formal and calling arg are to be flattened, leave that to argument
// expansion, not conversion.
if (!shouldFlatten(*function[param].type)) {
// Will make a two-level subtree.
// The deepest will copy member-by-member to build the structure to pass.
// The level above that will be a two-operand EOpComma sequence that follows the copy by the
// object itself.
TVariable* internalAggregate = makeInternalVariable("aggShadow", *function[param].type);
internalAggregate->getWritableType().getQualifier().makeTemporary();
TIntermSymbol* internalSymbolNode = new TIntermSymbol(internalAggregate->getUniqueId(),
internalAggregate->getName(),
internalAggregate->getType());
internalSymbolNode->setLoc(arg->getLoc());
// This makes the deepest level, the member-wise copy
TIntermAggregate* assignAgg = handleAssign(arg->getLoc(), EOpAssign, internalSymbolNode, arg)->getAsAggregate();
// Now, pair that with the resulting aggregate.
assignAgg = intermediate.growAggregate(assignAgg, internalSymbolNode, arg->getLoc());
assignAgg->setOperator(EOpComma);
assignAgg->setType(internalAggregate->getType());
setArg(i, assignAgg);
// Now, pair that with the resulting aggregate.
assignAgg = intermediate.growAggregate(assignAgg, internalSymbolNode, arg->getLoc());
assignAgg->setOperator(EOpComma);
assignAgg->setType(internalAggregate->getType());
setArg(param, assignAgg);
}
}
}
}
}
//
// Add any needed implicit expansion of calling arguments from what the shader listed to what's
// internally needed for the AST (given the constraints downstream).
//
void HlslParseContext::expandArguments(const TSourceLoc& loc, const TFunction& function, TIntermTyped*& arguments)
{
TIntermAggregate* aggregate = arguments->getAsAggregate();
int functionParamNumberOffset = 0;
// Replace a single argument with a single argument.
const auto setArg = [&](int paramNum, TIntermTyped* arg) {
if (function.getParamCount() + functionParamNumberOffset == 1)
arguments = arg;
else {
if (aggregate == nullptr)
arguments = arg;
else
aggregate->getSequence()[paramNum] = arg;
}
};
// Replace a single argument with a list of arguments
const auto setArgList = [&](int paramNum, const TVector<TIntermTyped*>& args) {
if (args.size() == 1)
setArg(paramNum, args.front());
else {
if (function.getParamCount() + functionParamNumberOffset == 1) {
arguments = intermediate.makeAggregate(args.front());
std::for_each(args.begin() + 1, args.end(),
[&](TIntermTyped* arg) {
arguments = intermediate.growAggregate(arguments, arg);
});
} else {
auto it = aggregate->getSequence().erase(aggregate->getSequence().begin() + paramNum);
aggregate->getSequence().insert(it, args.begin(), args.end());
}
}
functionParamNumberOffset += (args.size() - 1);
};
// Process each argument's conversion
for (int param = 0; param < function.getParamCount(); ++param) {
// At this early point there is a slight ambiguity between whether an aggregate 'arguments'
// is the single argument itself or its children are the arguments. Only one argument
// means take 'arguments' itself as the one argument.
TIntermTyped* arg = function.getParamCount() == 1
? arguments->getAsTyped()
: (aggregate ?
aggregate->getSequence()[param + functionParamNumberOffset]->getAsTyped() :
arguments->getAsTyped());
if (wasFlattened(arg) && shouldFlatten(*function[param].type)) {
// Need to pass the structure members instead of the structure.
TVector<TIntermTyped*> memberArgs;
for (int memb = 0; memb < (int)arg->getType().getStruct()->size(); ++memb) {
initFlattening();
memberArgs.push_back(flattenAccess(arg, memb));
finalizeFlattening();
}
setArgList(param + functionParamNumberOffset, memberArgs);
}
}
// TODO: if we need both hidden counter args (below) and struct expansion (above)
// the two algorithms need to be merged: Each assumes the list starts out 1:1 between
// parameters and arguments.
// If any argument is a pass-by-reference struct buffer with an associated counter
// buffer, we have to add another hidden parameter for that counter.
if (aggregate)
addStructBuffArguments(loc, aggregate);
}
//
// Add any needed implicit output conversions for function-call arguments. This
// can require a new tree topology, complicated further by whether the function
@@ -4682,7 +4800,7 @@ void HlslParseContext::addStructBuffArguments(const TSourceLoc& loc, TIntermAggr
TIntermSequence argsWithCounterBuffers;
for (int param=0; param<int(aggregate->getSequence().size()); ++param) {
for (int param = 0; param < int(aggregate->getSequence().size()); ++param) {
argsWithCounterBuffers.push_back(aggregate->getSequence()[param]);
if (hasStructBuffCounter(aggregate->getSequence()[param]->getAsTyped()->getType())) {
@@ -6769,7 +6887,7 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, const TStr
inheritGlobalDefaults(type.getQualifier());
const bool flattenVar = shouldFlattenUniform(type);
const bool flattenVar = shouldFlatten(type);
// correct IO in the type
switch (type.getQualifier().storage) {

View File

@@ -96,6 +96,7 @@ public:
void decomposeGeometryMethods(const TSourceLoc&, TIntermTyped*& node, TIntermNode* arguments);
void pushFrontArguments(TIntermTyped* front, TIntermTyped*& arguments);
void addInputArgumentConversions(const TFunction&, TIntermTyped*&);
void expandArguments(const TSourceLoc&, const TFunction&, TIntermTyped*&);
TIntermTyped* addOutputArgumentConversions(const TFunction&, TIntermOperator&);
void builtInOpCheck(const TSourceLoc&, const TFunction&, TIntermOperator&);
TFunction* makeConstructorCall(const TSourceLoc&, const TType&);
@@ -236,13 +237,14 @@ protected:
// Array and struct flattening
TIntermTyped* flattenAccess(TIntermTyped* base, int member);
bool shouldFlattenUniform(const TType&) const;
TIntermTyped* flattenAccess(int uniqueId, int member, const TType&);
bool shouldFlatten(const TType&) const;
bool wasFlattened(const TIntermTyped* node) const;
bool wasFlattened(int id) const { return flattenMap.find(id) != flattenMap.end(); }
int addFlattenedMember(const TSourceLoc& loc, const TVariable&, const TType&, TFlattenData&, const TString& name, bool track);
bool isFinalFlattening(const TType& type) const { return !(type.isStruct() || type.isArray()); }
// Structure splitting (splits interstage builtin types into its own struct)
// Structure splitting (splits interstage built-in types into its own struct)
TIntermTyped* splitAccessStruct(const TSourceLoc& loc, TIntermTyped*& base, int& member);
void splitAccessArray(const TSourceLoc& loc, TIntermTyped* base, TIntermTyped* index);
TType& split(TType& type, TString name, const TType* outerStructType = nullptr);

View File

@@ -502,7 +502,7 @@ void TBuiltInParseablesHlsl::initialize(int /*version*/, EProfile /*profile*/, c
static const EShLanguageMask EShLangAll = EShLanguageMask(EShLangCount - 1);
// These are the actual stage masks defined in the documentation, in case they are
// needed for furture validation. For now, they are commented out, and set below
// needed for future validation. For now, they are commented out, and set below
// to EShLangAll, to allow any intrinsic to be used in any shader, which is legal
// if it is not called.
//