WIP: HLSL: add structuredbuffer pass by reference in fn params
This PR adds the ability to pass structuredbuffer types by reference as function parameters. It also changes the representation of structuredbuffers from anonymous blocks with named members, to named blocks with pseudonymous members. That should not be an externally visible change.
This commit is contained in:
parent
4a57dced66
commit
dd8287a109
15 changed files with 975 additions and 558 deletions
|
|
@ -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:
|
||||
{
|
||||
|
|
@ -3643,7 +3663,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);
|
||||
|
|
@ -5033,6 +5053,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) {
|
||||
|
|
@ -5043,6 +5158,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;
|
||||
}
|
||||
|
|
@ -5914,6 +6041,7 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, TString& i
|
|||
if (it != ioTypeMap.end())
|
||||
type.setStruct(it->second.uniform);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -6551,8 +6679,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:
|
||||
|
|
@ -6564,10 +6690,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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue