Merge pull request #736 from steve-lunarg/structbuffer-params
HLSL: add structuredbuffer pass by reference in fn params
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user