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:
steve-lunarg
2017-02-23 18:04:12 -07:00
parent 4a57dced66
commit dd8287a109
15 changed files with 975 additions and 558 deletions

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:
{
@@ -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);