Merge pull request #736 from steve-lunarg/structbuffer-params

HLSL: add structuredbuffer pass by reference in fn params
This commit is contained in:
John Kessenich
2017-02-28 13:10:51 -07:00
committed by GitHub
15 changed files with 975 additions and 558 deletions

View File

@@ -406,36 +406,13 @@ bool HlslGrammar::acceptDeclaration(TIntermNode*& node, TIntermNode*& node2)
}
}
TString* blockName = idToken.string;
// For structbuffers, we couldn't create the block type while accepting the
// template type, because we need the identifier name. Now that we have that,
// we can create the buffer type.
// TODO: how to determine this without looking for implicit array sizes?
if (variableType.getBasicType() == EbtBlock) {
const int memberCount = variableType.getStruct()->size();
assert(memberCount > 0);
TType* contentType = (*variableType.getStruct())[memberCount-1].type;
// Set the field name and qualifier from the declaration, now that we know it.
if (contentType->isRuntimeSizedArray()) {
contentType->getQualifier() = variableType.getQualifier();
blockName = nullptr; // this will be an anonymous block...
contentType->setFieldName(*idToken.string); // field name is declaration name
variableType.setTypeName(*idToken.string);
}
}
// Hand off the actual declaration
// TODO: things scoped within an annotation need their own name space;
// TODO: strings are not yet handled.
if (variableType.getBasicType() != EbtString && parseContext.getAnnotationNestingLevel() == 0) {
if (typedefDecl)
parseContext.declareTypedef(idToken.loc, *idToken.string, variableType);
else if (variableType.getBasicType() == EbtBlock)
parseContext.declareBlock(idToken.loc, variableType, blockName);
parseContext.declareBlock(idToken.loc, variableType, idToken.string);
else {
if (variableType.getQualifier().storage == EvqUniform && ! variableType.containsOpaque()) {
// this isn't really an individual variable, but a member of the $Global buffer
@@ -1888,18 +1865,26 @@ bool HlslGrammar::acceptStructBufferType(TType& type)
TArraySizes unsizedArray;
unsizedArray.addInnerSize(UnsizedArraySize);
templateType->newArraySizes(unsizedArray);
templateType->getQualifier().storage = storage;
templateType->getQualifier().readonly = readonly;
// field name is canonical for all structbuffers
templateType->setFieldName("@data");
// Create block type. TODO: hidden internal uint member when needed
TTypeList* blockStruct = new TTypeList;
TTypeLoc member = { templateType, token.loc };
blockStruct->push_back(member);
// This is the type of the buffer block (SSBO)
TType blockType(blockStruct, "", templateType->getQualifier());
// It's not until we see the name during declaration that we can set the
// field name. That happens in HlslGrammar::acceptDeclaration.
blockType.getQualifier().storage = storage;
blockType.getQualifier().readonly = readonly;
// We may have created an equivalent type before, in which case we should use its
// deep structure.
parseContext.shareStructBufferType(blockType);
type.shallowCopy(blockType);
return true;

View File

@@ -691,6 +691,22 @@ TIntermTyped* HlslParseContext::handleBracketOperator(const TSourceLoc& loc, TIn
}
}
// Handle operator[] on structured buffers: this indexes into the array element of the buffer.
// indexStructBufferContent returns nullptr if it isn't a structuredbuffer (SSBO).
TIntermTyped* sbArray = indexStructBufferContent(loc, base);
if (sbArray != nullptr) {
if (sbArray == nullptr)
return nullptr;
// Now we'll apply the [] index to that array
const TOperator idxOp = (index->getQualifier().storage == EvqConst) ? EOpIndexDirect : EOpIndexIndirect;
TIntermTyped* element = intermediate.addIndex(idxOp, sbArray, index, loc);
const TType derefType(sbArray->getType(), 0);
element->setType(derefType);
return element;
}
return nullptr;
}
@@ -866,9 +882,7 @@ TIntermTyped* HlslParseContext::handleDotDereference(const TSourceLoc& loc, TInt
const int vecSize = sampler.isShadow() ? 1 : 4; // TODO: handle arbitrary sample return sizes
return intermediate.addMethod(base, TType(sampler.type, EvqTemporary, vecSize), &field, loc);
}
} else if (isStructBufferMethod(field) &&
base->getType().isRuntimeSizedArray() &&
(base->getQualifier().storage == EvqUniform || base->getQualifier().storage == EvqBuffer)) {
} else if (isStructBufferType(base->getType())) {
TType retType(base->getType(), 0);
return intermediate.addMethod(base, retType, &field, loc);
} else if (field == "Append" ||
@@ -1919,9 +1933,11 @@ void HlslParseContext::remapNonEntryPointIO(TFunction& function)
if (function.getType().getBasicType() != EbtVoid)
clearUniformInputOutput(function.getWritableType().getQualifier());
// parameters
// parameters.
// References to structuredbuffer types are left unmodified
for (int i = 0; i < function.getParamCount(); i++)
clearUniformInputOutput(function[i].type->getQualifier());
if (!isReference(*function[i].type))
clearUniformInputOutput(function[i].type->getQualifier());
}
// Handle function returns, including type conversions to the function return type
@@ -2284,13 +2300,17 @@ void HlslParseContext::decomposeStructBufferMethods(const TSourceLoc& loc, TInte
const TOperator op = node->getAsOperator()->getOp();
TIntermAggregate* argAggregate = arguments ? arguments->getAsAggregate() : nullptr;
TIntermTyped* argArray = argAggregate ? argAggregate->getSequence()[0]->getAsTyped() : nullptr; // array
// Bail out if not a block method
if (argArray == nullptr || !argArray->getType().isRuntimeSizedArray())
if (argAggregate == nullptr)
return;
// Buffer is the object upon which method is called, so always arg 0
TIntermTyped* bufferObj = argAggregate->getSequence()[0]->getAsTyped();
// Index to obtain the runtime sized array out of the buffer.
TIntermTyped* argArray = indexStructBufferContent(loc, bufferObj);
if (argArray == nullptr)
return; // It might not be a struct buffer method.
switch (op) {
case EOpMethodLoad:
{
@@ -3740,7 +3760,7 @@ TIntermTyped* HlslParseContext::handleFunctionCall(const TSourceLoc& loc, TFunct
// the symbol table for an arbitrary type. This is a temporary hack until that ability exists.
// It will have false positives, since it doesn't check arg counts or types.
if (arguments && arguments->getAsAggregate()) {
if (arguments->getAsAggregate()->getSequence()[0]->getAsTyped()->getType().isRuntimeSizedArray()) {
if (isStructBufferType(arguments->getAsAggregate()->getSequence()[0]->getAsTyped()->getType())) {
if (isStructBufferMethod(function->getName())) {
const TString mangle = function->getName() + "(";
TSymbol* symbol = symbolTable.find(mangle, &builtIn);
@@ -5130,6 +5150,101 @@ void HlslParseContext::redeclareBuiltinBlock(const TSourceLoc& loc, TTypeList& n
trackLinkage(*block);
}
//
// Generate index to the array element in a structure buffer (SSBO)
//
TIntermTyped* HlslParseContext::indexStructBufferContent(const TSourceLoc& loc, TIntermTyped* buffer) const
{
// Bail out if not a struct buffer
if (buffer == nullptr || ! isStructBufferType(buffer->getType()))
return nullptr;
// Runtime sized array is always the last element.
const TTypeList* bufferStruct = buffer->getType().getStruct();
TIntermTyped* arrayPosition = intermediate.addConstantUnion(unsigned(bufferStruct->size()-1), loc);
TIntermTyped* argArray = intermediate.addIndex(EOpIndexDirectStruct, buffer, arrayPosition, loc);
argArray->setType(*(*bufferStruct)[bufferStruct->size()-1].type);
return argArray;
}
//
// IFF type is a structuredbuffer/byteaddressbuffer type, return the content
// (template) type. E.g, StructuredBuffer<MyType> -> MyType. Else return nullptr.
//
TType* HlslParseContext::getStructBufferContentType(const TType& type) const
{
if (type.getBasicType() != EbtBlock)
return nullptr;
const int memberCount = type.getStruct()->size();
assert(memberCount > 0);
TType* contentType = (*type.getStruct())[memberCount-1].type;
return contentType->isRuntimeSizedArray() ? contentType : nullptr;
}
//
// If an existing struct buffer has a sharable type, then share it.
//
void HlslParseContext::shareStructBufferType(TType& type)
{
// PackOffset must be equivalent to share types on a per-member basis.
// Note: cannot use auto type due to recursion. Thus, this is a std::function.
const std::function<bool(TType& lhs, TType& rhs)>
compareQualifiers = [&](TType& lhs, TType& rhs) -> bool {
if (lhs.getQualifier().layoutOffset != rhs.getQualifier().layoutOffset)
return false;
if (lhs.isStruct() != rhs.isStruct())
return false;
if (lhs.isStruct() && rhs.isStruct()) {
if (lhs.getStruct()->size() != rhs.getStruct()->size())
return false;
for (int i = 0; i < int(lhs.getStruct()->size()); ++i)
if (!compareQualifiers(*(*lhs.getStruct())[i].type, *(*rhs.getStruct())[i].type))
return false;
}
return true;
};
// We need to compare certain qualifiers in addition to the type.
const auto typeEqual = [compareQualifiers](TType& lhs, TType& rhs) -> bool {
if (lhs.getQualifier().readonly != rhs.getQualifier().readonly)
return false;
// If both are structures, recursively look for packOffset equality
// as well as type equality.
return compareQualifiers(lhs, rhs) && lhs == rhs;
};
// TString typeName;
// type.appendMangledName(typeName);
// type.setTypeName(typeName);
// This is an exhaustive O(N) search, but real world shaders have
// only a small number of these.
for (int idx = 0; idx < int(structBufferTypes.size()); ++idx) {
// If the deep structure matches, modulo qualifiers, use it
if (typeEqual(*structBufferTypes[idx], type)) {
type.shallowCopy(*structBufferTypes[idx]);
return;
}
}
// Otherwise, remember it:
TType* typeCopy = new TType;
typeCopy->shallowCopy(type);
structBufferTypes.push_back(typeCopy);
// structBuffTypes.push_back(type.getWritableStruct());
}
void HlslParseContext::paramFix(TType& type)
{
switch (type.getQualifier().storage) {
@@ -5140,6 +5255,18 @@ void HlslParseContext::paramFix(TType& type)
case EvqTemporary:
type.getQualifier().storage = EvqIn;
break;
case EvqBuffer:
{
// SSBO parameter. These do not go through the declareBlock path since they are fn parameters.
correctUniform(type.getQualifier());
TQualifier bufferQualifier = globalBufferDefaults;
mergeObjectLayoutQualifiers(bufferQualifier, type.getQualifier(), true);
bufferQualifier.storage = type.getQualifier().storage;
bufferQualifier.readonly = type.getQualifier().readonly;
bufferQualifier.coherent = type.getQualifier().coherent;
type.getQualifier() = bufferQualifier;
break;
}
default:
break;
}
@@ -6011,6 +6138,7 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, TString& i
if (it != ioTypeMap.end())
type.setStruct(it->second.uniform);
}
break;
default:
break;
@@ -6648,8 +6776,6 @@ void HlslParseContext::declareBlock(const TSourceLoc& loc, TType& type, const TS
error(memberLoc, "member cannot contradict block (or what block inherited from global)", "xfb_buffer", "");
}
if (memberQualifier.hasPacking())
error(memberLoc, "member of block cannot have a packing layout qualifier", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.hasLocation()) {
switch (type.getQualifier().storage) {
case EvqVaryingIn:
@@ -6661,10 +6787,6 @@ void HlslParseContext::declareBlock(const TSourceLoc& loc, TType& type, const TS
}
} else
memberWithoutLocation = true;
if (memberQualifier.hasAlign()) {
if (defaultQualification.layoutPacking != ElpStd140 && defaultQualification.layoutPacking != ElpStd430)
error(memberLoc, "can only be used with std140 or std430 layout packing", "align", "");
}
TQualifier newMemberQualification = defaultQualification;
mergeQualifiers(newMemberQualification, memberQualifier);

View File

@@ -180,6 +180,9 @@ public:
void initFlattening() { flattenLevel.push_back(0); flattenOffset.push_back(0); }
void finalizeFlattening() { flattenLevel.pop_back(); flattenOffset.pop_back(); }
// Share struct buffer deep types
void shareStructBufferType(TType&);
protected:
struct TFlattenData {
TFlattenData() : nextBinding(TQualifier::layoutBindingEnd) { }
@@ -248,6 +251,14 @@ protected:
bool isSamplerMethod(const TString& name) const;
bool isStructBufferMethod(const TString& name) const;
TType* getStructBufferContentType(const TType& type) const;
bool isStructBufferType(const TType& type) const { return getStructBufferContentType(type) != nullptr; }
TIntermTyped* indexStructBufferContent(const TSourceLoc& loc, TIntermTyped* buffer) const;
// Return true if this type is a reference. This is not currently a type method in case that's
// a language specific answer.
bool isReference(const TType& type) const { return isStructBufferType(type); }
// Pass through to base class after remembering builtin mappings.
using TParseContextBase::trackLinkage;
void trackLinkage(TSymbol& variable) override;
@@ -330,6 +341,9 @@ protected:
// Structure splitting data:
TMap<int, TVariable*> splitIoVars; // variables with the builtin interstage IO removed, indexed by unique ID.
// Structuredbuffer shared types. Typically there are only a few.
TVector<TType*> structBufferTypes;
// The builtin interstage IO map considers e.g, EvqPosition on input and output separately, so that we
// can build the linkage correctly if position appears on both sides. Otherwise, multiple positions
// are considered identical.