Commit 5da1f038 by steve-lunarg

HLSL: implement 4 (of 6) structuredbuffer types

This is a partial implemention of structurebuffers supporting: * structured buffer types of: * StructuredBuffer * RWStructuredBuffer * ByteAddressBuffer * RWByteAddressBuffer * Atomic operations on RWByteAddressBuffer * Load/Load[234], Store/Store[234], GetDimensions methods (where allowed by type) * globallycoherent flag But NOT yet supporting: * AppendStructuredBuffer / ConsumeStructuredBuffer types * IncrementCounter/DecrementCounter methods Please note: the stride returned by GetDimensions is as calculated by glslang for std430, and may not match other environments in all cases.
parent c8aed915
......@@ -1125,7 +1125,8 @@ Id Builder::createAccessChain(StorageClass storageClass, Id base, const std::vec
Id Builder::createArrayLength(Id base, unsigned int member)
{
Instruction* length = new Instruction(getUniqueId(), makeIntType(32), OpArrayLength);
spv::Id intType = makeIntType(32);
Instruction* length = new Instruction(getUniqueId(), intType, OpArrayLength);
length->addIdOperand(base);
length->addImmediateOperand(member);
buildPoint->addInstruction(std::unique_ptr<Instruction>(length));
......
RWByteAddressBuffer sbuf;
float4 main(uint pos : FOO) : SV_Target0
{
uint u;
sbuf.InterlockedAdd(8, 1);
sbuf.InterlockedAdd(8, 1, u);
sbuf.InterlockedAnd(8, 1);
sbuf.InterlockedAnd(8, 1, u);
sbuf.InterlockedCompareExchange(8, 1, 2, u);
// sbuf.InterlockedCompareStore(8, 1, 2); // TODO: ...
sbuf.InterlockedExchange(8, 1, u);
sbuf.InterlockedMax(8, 1);
sbuf.InterlockedMax(8, 1, u);
sbuf.InterlockedMin(8, 1);
sbuf.InterlockedMin(8, 1, u);
sbuf.InterlockedOr(8, 1);
sbuf.InterlockedOr(8, 1, u);
sbuf.InterlockedXor(8, 1);
sbuf.InterlockedXor(8, 1, u);
return sbuf.Load(pos);
}
ByteAddressBuffer sbuf;
float4 main(uint pos : FOO) : SV_Target0
{
uint size;
sbuf.GetDimensions(size);
return sbuf.Load(pos) +
float4(sbuf.Load2(pos+4), 0, 0) +
float4(sbuf.Load3(pos+8), 0) +
sbuf.Load4(pos+12);
}
struct sb_t
{
float3 color;
bool test;
};
globallycoherent RWStructuredBuffer<sb_t> sbuf;
globallycoherent RWStructuredBuffer<float> sbuf2;
float4 main(uint pos : FOO) : SV_Target0
{
sbuf2[pos+1] = 42;
uint size;
uint stride;
sbuf.GetDimensions(size, stride);
if (sbuf[pos].test)
return float4(sbuf[pos].color + sbuf2[pos], 0);
else
return size + stride;
}
struct sb_t
{
float3 color;
bool test;
bool test2;
}; // stride = 20
StructuredBuffer<sb_t> sbuf : register(c10);
StructuredBuffer<float> sbuf2;
float4 main(uint pos : FOO) : SV_Target0
{
sb_t mydata = sbuf.Load(pos);
uint size;
uint stride;
sbuf.GetDimensions(size, stride);
if (sbuf[pos].test)
return float4(sbuf[pos].color + sbuf2[pos], 0);
else
return mydata.color.x + size + stride;
}
struct sb_t
{
float3 color;
bool test;
};
RWStructuredBuffer<sb_t> sbuf;
RWStructuredBuffer<float> sbuf2;
float4 main(uint pos : FOO) : SV_Target0
{
sbuf2[pos+1] = 42;
uint size;
uint stride;
sbuf.GetDimensions(size, stride);
if (sbuf[pos].test)
return float4(sbuf[pos].color + sbuf2[pos], 0);
else
return size + stride;
}
RWByteAddressBuffer sbuf;
float4 main(uint pos : FOO) : SV_Target0
{
uint size;
sbuf.GetDimensions(size);
sbuf.Store(pos, sbuf.Load(pos));
sbuf.Store2(pos, sbuf.Load2(pos));
sbuf.Store3(pos, sbuf.Load3(pos));
sbuf.Store4(pos, sbuf.Load4(pos));
return sbuf.Load(pos);
}
......@@ -624,6 +624,15 @@ enum TOperator {
EOpMethodCalculateLevelOfDetail, // ...
EOpMethodCalculateLevelOfDetailUnclamped, // ...
// Load already defined above for textures
EOpMethodLoad2, // Structure buffer object methods. These are translated to existing
EOpMethodLoad3, // AST methods, and exist to represent HLSL semantics until that
EOpMethodLoad4, // translation is performed. See HlslParseContext::decomposeSampleMethods().
EOpMethodStore, // ...
EOpMethodStore2, // ...
EOpMethodStore3, // ...
EOpMethodStore4, // ...
// SM5 texture methods
EOpMethodGatherRed, // These are covered under the above EOpMethodSample comment about
EOpMethodGatherGreen, // translation to existing AST opcodes. They exist temporarily
......
......@@ -221,6 +221,12 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.struct.split.trivial.vert", "main"},
{"hlsl.structarray.flatten.frag", "main"},
{"hlsl.structarray.flatten.geom", "main"},
{"hlsl.structbuffer.frag", "main"},
{"hlsl.structbuffer.atomics.frag", "main"},
{"hlsl.structbuffer.byte.frag", "main"},
{"hlsl.structbuffer.coherent.frag", "main"},
{"hlsl.structbuffer.rw.frag", "main"},
{"hlsl.structbuffer.rwbyte.frag", "main"},
{"hlsl.structin.vert", "main"},
{"hlsl.structIoFourWay.frag", "main"},
{"hlsl.intrinsics.vert", "VertexShaderFunction"},
......
......@@ -406,6 +406,27 @@ 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;
......@@ -414,7 +435,7 @@ bool HlslGrammar::acceptDeclaration(TIntermNode*& node, TIntermNode*& node2)
if (typedefDecl)
parseContext.declareTypedef(idToken.loc, *idToken.string, variableType);
else if (variableType.getBasicType() == EbtBlock)
parseContext.declareBlock(idToken.loc, variableType, idToken.string);
parseContext.declareBlock(idToken.loc, variableType, blockName);
else {
if (variableType.getQualifier().storage == EvqUniform && ! variableType.containsOpaque()) {
// this isn't really an individual variable, but a member of the $Global buffer
......@@ -533,8 +554,11 @@ bool HlslGrammar::acceptFullySpecifiedType(TType& type)
qualifier.layoutFormat = type.getQualifier().layoutFormat;
qualifier.precision = type.getQualifier().precision;
if (type.getQualifier().storage == EvqVaryingOut)
if (type.getQualifier().storage == EvqVaryingOut ||
type.getQualifier().storage == EvqBuffer) {
qualifier.storage = type.getQualifier().storage;
qualifier.readonly = type.getQualifier().readonly;
}
type.getQualifier() = qualifier;
}
......@@ -609,6 +633,9 @@ bool HlslGrammar::acceptQualifier(TQualifier& qualifier)
if (! acceptLayoutQualifierList(qualifier))
return false;
continue;
case EHTokGloballyCoherent:
qualifier.coherent = true;
break;
// GS geometries: these are specified on stage input variables, and are an error (not verified here)
// for output variables.
......@@ -1246,11 +1273,19 @@ bool HlslGrammar::acceptType(TType& type)
return acceptTextureType(type);
break;
case EHTokAppendStructuredBuffer:
case EHTokByteAddressBuffer:
case EHTokConsumeStructuredBuffer:
case EHTokRWByteAddressBuffer:
case EHTokRWStructuredBuffer:
case EHTokStructuredBuffer:
return acceptStructBufferType(type);
break;
case EHTokStruct:
case EHTokCBuffer:
case EHTokTBuffer:
return acceptStruct(type);
break;
case EHTokIdentifier:
// An identifier could be for a user-defined type.
......@@ -1783,6 +1818,93 @@ bool HlslGrammar::acceptStruct(TType& type)
return true;
}
// struct_buffer
// : APPENDSTRUCTUREDBUFFER
// | BYTEADDRESSBUFFER
// | CONSUMESTRUCTUREDBUFFER
// | RWBYTEADDRESSBUFFER
// | RWSTRUCTUREDBUFFER
// | STRUCTUREDBUFFER
bool HlslGrammar::acceptStructBufferType(TType& type)
{
const EHlslTokenClass structBuffType = peek();
// TODO: globallycoherent
bool hasTemplateType = true;
bool readonly = false;
TStorageQualifier storage = EvqBuffer;
switch (structBuffType) {
case EHTokAppendStructuredBuffer:
unimplemented("AppendStructuredBuffer");
return false;
case EHTokByteAddressBuffer:
hasTemplateType = false;
readonly = true;
break;
case EHTokConsumeStructuredBuffer:
unimplemented("ConsumeStructuredBuffer");
return false;
case EHTokRWByteAddressBuffer:
hasTemplateType = false;
break;
case EHTokRWStructuredBuffer:
break;
case EHTokStructuredBuffer:
readonly = true;
break;
default:
return false; // not a structure buffer type
}
advanceToken(); // consume the structure keyword
// type on which this StructedBuffer is templatized. E.g, StructedBuffer<MyStruct> ==> MyStruct
TType* templateType = new TType;
if (hasTemplateType) {
if (! acceptTokenClass(EHTokLeftAngle)) {
expected("left angle bracket");
return false;
}
if (! acceptType(*templateType)) {
expected("type");
return false;
}
if (! acceptTokenClass(EHTokRightAngle)) {
expected("right angle bracket");
return false;
}
} else {
// byte address buffers have no explicit type.
TType uintType(EbtUint, storage);
templateType->shallowCopy(uintType);
}
// Create an unsized array out of that type.
// TODO: does this work if it's already an array type?
TArraySizes unsizedArray;
unsizedArray.addInnerSize(UnsizedArraySize);
templateType->newArraySizes(unsizedArray);
templateType->getQualifier().storage = storage;
templateType->getQualifier().readonly = readonly;
// Create block type. TODO: hidden internal uint member when needed
TTypeList* blockStruct = new TTypeList;
TTypeLoc member = { templateType, token.loc };
blockStruct->push_back(member);
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.
type.shallowCopy(blockType);
return true;
}
// struct_declaration_list
// : struct_declaration SEMI_COLON struct_declaration SEMI_COLON ...
//
......
......@@ -83,6 +83,7 @@ namespace glslang {
bool acceptAnnotations(TQualifier&);
bool acceptSamplerType(TType&);
bool acceptTextureType(TType&);
bool acceptStructBufferType(TType&);
bool acceptStruct(TType&);
bool acceptStructDeclarationList(TTypeList*&);
bool acceptFunctionParameters(TFunction&);
......
......@@ -84,6 +84,7 @@ public:
TIntermTyped* handleFunctionCall(const TSourceLoc&, TFunction*, TIntermTyped*);
void decomposeIntrinsic(const TSourceLoc&, TIntermTyped*& node, TIntermNode* arguments);
void decomposeSampleMethods(const TSourceLoc&, TIntermTyped*& node, TIntermNode* arguments);
void decomposeStructBufferMethods(const TSourceLoc&, TIntermTyped*& node, TIntermNode* arguments);
void decomposeGeometryMethods(const TSourceLoc&, TIntermTyped*& node, TIntermNode* arguments);
TIntermTyped* handleLengthMethod(const TSourceLoc&, TFunction*, TIntermNode*);
void addInputArgumentConversions(const TFunction&, TIntermTyped*&);
......@@ -243,6 +244,10 @@ protected:
void correctUniform(TQualifier& qualifier);
void clearUniformInputOutput(TQualifier& qualifier);
// Test method names
bool isSamplerMethod(const TString& name) const;
bool isStructBufferMethod(const TString& name) const;
// Pass through to base class after remembering builtin mappings.
using TParseContextBase::trackLinkage;
void trackLinkage(TSymbol& variable) override;
......
......@@ -850,6 +850,26 @@ void TBuiltInParseablesHlsl::initialize(int /*version*/, EProfile /*profile*/, c
{ "Append", "-", "-", "-", "-", EShLangGS },
{ "RestartStrip", "-", "-", "-", "-", EShLangGS },
// Methods for structurebuffers. TODO: wildcard type matching.
{ "Load", nullptr, nullptr, "-", "-", EShLangAll },
{ "Load2", nullptr, nullptr, "-", "-", EShLangAll },
{ "Load3", nullptr, nullptr, "-", "-", EShLangAll },
{ "Load4", nullptr, nullptr, "-", "-", EShLangAll },
{ "Store", nullptr, nullptr, "-", "-", EShLangAll },
{ "Store2", nullptr, nullptr, "-", "-", EShLangAll },
{ "Store3", nullptr, nullptr, "-", "-", EShLangAll },
{ "Store4", nullptr, nullptr, "-", "-", EShLangAll },
{ "GetDimensions", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedAdd", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedAnd", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedCompareExchange", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedCompareStore", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedExchange", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedMax", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedMin", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedOr", nullptr, nullptr, "-", "-", EShLangAll },
{ "InterlockedXor", nullptr, nullptr, "-", "-", EShLangAll },
// Mark end of list, since we want to avoid a range-based for, as some compilers don't handle it yet.
{ nullptr, nullptr, nullptr, nullptr, nullptr, 0 },
};
......@@ -1144,6 +1164,15 @@ void TBuiltInParseablesHlsl::identifyBuiltIns(int /*version*/, EProfile /*profil
symbolTable.relateToOperator("CalculateLevelOfDetail", EOpMethodCalculateLevelOfDetail);
symbolTable.relateToOperator("CalculateLevelOfDetailUnclamped", EOpMethodCalculateLevelOfDetailUnclamped);
// Structure buffer methods (excluding associations already made above for texture methods w/ same name)
symbolTable.relateToOperator("Load2", EOpMethodLoad2);
symbolTable.relateToOperator("Load3", EOpMethodLoad3);
symbolTable.relateToOperator("Load4", EOpMethodLoad4);
symbolTable.relateToOperator("Store", EOpMethodStore);
symbolTable.relateToOperator("Store2", EOpMethodStore2);
symbolTable.relateToOperator("Store3", EOpMethodStore3);
symbolTable.relateToOperator("Store4", EOpMethodStore4);
// SM5 Texture methods
symbolTable.relateToOperator("GatherRed", EOpMethodGatherRed);
symbolTable.relateToOperator("GatherGreen", EOpMethodGatherGreen);
......
......@@ -118,6 +118,7 @@ void HlslScanContext::fillInKeywordMap()
(*KeywordMap)["out"] = EHTokOut;
(*KeywordMap)["inout"] = EHTokInOut;
(*KeywordMap)["layout"] = EHTokLayout;
(*KeywordMap)["globallycoherent"] = EHTokGloballyCoherent;
(*KeywordMap)["point"] = EHTokPoint;
(*KeywordMap)["line"] = EHTokLine;
......@@ -319,6 +320,13 @@ void HlslScanContext::fillInKeywordMap()
(*KeywordMap)["RWTexture3D"] = EHTokRWTexture3d;
(*KeywordMap)["RWBuffer"] = EHTokRWBuffer;
(*KeywordMap)["AppendStructuredBuffer"] = EHTokAppendStructuredBuffer;
(*KeywordMap)["ByteAddressBuffer"] = EHTokByteAddressBuffer;
(*KeywordMap)["ConsumeStructuredBuffer"] = EHTokConsumeStructuredBuffer;
(*KeywordMap)["RWByteAddressBuffer"] = EHTokRWByteAddressBuffer;
(*KeywordMap)["RWStructuredBuffer"] = EHTokRWStructuredBuffer;
(*KeywordMap)["StructuredBuffer"] = EHTokStructuredBuffer;
(*KeywordMap)["struct"] = EHTokStruct;
(*KeywordMap)["cbuffer"] = EHTokCBuffer;
(*KeywordMap)["tbuffer"] = EHTokTBuffer;
......@@ -527,6 +535,7 @@ EHlslTokenClass HlslScanContext::tokenizeIdentifier()
case EHTokInOut:
case EHTokPrecise:
case EHTokLayout:
case EHTokGloballyCoherent:
return keyword;
// primitive types
......@@ -722,6 +731,12 @@ EHlslTokenClass HlslScanContext::tokenizeIdentifier()
case EHTokRWTexture2darray:
case EHTokRWTexture3d:
case EHTokRWBuffer:
case EHTokAppendStructuredBuffer:
case EHTokByteAddressBuffer:
case EHTokConsumeStructuredBuffer:
case EHTokRWByteAddressBuffer:
case EHTokRWStructuredBuffer:
case EHTokStructuredBuffer:
return keyword;
// variable, user type, ...
......
......@@ -65,6 +65,7 @@ enum EHlslTokenClass {
EHTokOut,
EHTokInOut,
EHTokLayout,
EHTokGloballyCoherent,
// primitive types
EHTokPoint,
......@@ -256,6 +257,14 @@ enum EHlslTokenClass {
EHTokRWTexture3d,
EHTokRWBuffer,
// Structure buffer variants
EHTokAppendStructuredBuffer,
EHTokByteAddressBuffer,
EHTokConsumeStructuredBuffer,
EHTokRWByteAddressBuffer,
EHTokRWStructuredBuffer,
EHTokStructuredBuffer,
// variable, user type, ...
EHTokIdentifier,
EHTokTypeName,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment