Merge pull request #1017 from LoopDawg/texture-struct-return.1

HLSL: add methods to handle user structures in texture template type.
This commit is contained in:
John Kessenich 2017-08-19 16:00:26 -06:00 committed by GitHub
commit b207daa5d3
9 changed files with 1516 additions and 41 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
struct s1_t {
float c0;
float2 c1;
float c2;
};
struct s2_t {
float c0;
float3 c1;
};
struct s3_t {
float2 c0;
float1 c1;
};
struct s4_t {
int c0;
int2 c1;
int c2;
};
struct s5_t {
uint c0;
uint c1;
};
SamplerState g_sSamp;
Texture2D <s1_t> g_tTex2s1;
Texture2D <s2_t> g_tTex2s2;
Texture2D <s3_t> g_tTex2s3;
Texture2D <s4_t> g_tTex2s4;
Texture2D <s5_t> g_tTex2s5;
Texture2D <s1_t> g_tTex2s1a; // same type as g_tTex2s1, to test fn signature matching.
// function overloading to test name mangling with textures templatized on structs
s1_t fn1(Texture2D <s1_t> t1) { return t1 . Sample(g_sSamp, float2(0.6, 0.61)); }
s2_t fn1(Texture2D <s2_t> t2) { return t2 . Sample(g_sSamp, float2(0.6, 0.61)); }
float4 main() : SV_Target0
{
s1_t s1 = g_tTex2s1 . Sample(g_sSamp, float2(0.1, 0.11));
s2_t s2 = g_tTex2s2 . Sample(g_sSamp, float2(0.2, 0.21));
s3_t s3 = g_tTex2s3 . Sample(g_sSamp, float2(0.3, 0.31));
s4_t s4 = g_tTex2s4 . Sample(g_sSamp, float2(0.4, 0.41));
s5_t s5 = g_tTex2s5 . Sample(g_sSamp, float2(0.5, 0.51));
s1_t r0 = fn1(g_tTex2s1);
s2_t r1 = fn1(g_tTex2s2);
s1_t r2 = fn1(g_tTex2s1a);
return 0;
}

View File

@ -11,7 +11,7 @@ Texture2D <float4> g_tTex2df4;
SamplerState g_sSamp; SamplerState g_sSamp;
float4 main() float4 main() : SV_Target0
{ {
uint MipLevel; uint MipLevel;
uint WidthU; uint WidthU;

View File

@ -80,7 +80,19 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
bool combined : 1; // true means texture is combined with a sampler, false means texture with no sampler bool combined : 1; // true means texture is combined with a sampler, false means texture with no sampler
bool sampler : 1; // true means a pure sampler, other fields should be clear() bool sampler : 1; // true means a pure sampler, other fields should be clear()
bool external : 1; // GL_OES_EGL_image_external bool external : 1; // GL_OES_EGL_image_external
unsigned int vectorSize : 3; // return vector size. TODO: support arbitrary types. unsigned int vectorSize : 3; // vector return type size.
// Some languages support structures as sample results. Storing the whole structure in the
// TSampler is too large, so there is an index to a separate table.
static const unsigned structReturnIndexBits = 4; // number of index bits to use.
static const unsigned structReturnSlots = (1<<structReturnIndexBits)-1; // number of valid values
static const unsigned noReturnStruct = structReturnSlots; // value if no return struct type.
// Index into a language specific table of texture return structures.
unsigned int structReturnIndex : structReturnIndexBits;
// Encapsulate getting members' vector sizes packed into the vectorSize bitfield.
unsigned int getVectorSize() const { return vectorSize; }
bool isImage() const { return image && dim != EsdSubpass; } bool isImage() const { return image && dim != EsdSubpass; }
bool isSubpass() const { return dim == EsdSubpass; } bool isSubpass() const { return dim == EsdSubpass; }
@ -90,6 +102,7 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
bool isShadow() const { return shadow; } bool isShadow() const { return shadow; }
bool isArrayed() const { return arrayed; } bool isArrayed() const { return arrayed; }
bool isMultiSample() const { return ms; } bool isMultiSample() const { return ms; }
bool hasReturnStruct() const { return structReturnIndex != noReturnStruct; }
void clear() void clear()
{ {
@ -102,6 +115,9 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
combined = false; combined = false;
sampler = false; sampler = false;
external = false; external = false;
structReturnIndex = noReturnStruct;
// by default, returns a single vec4;
vectorSize = 4; vectorSize = 4;
} }
@ -160,16 +176,17 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
bool operator==(const TSampler& right) const bool operator==(const TSampler& right) const
{ {
return type == right.type && return type == right.type &&
dim == right.dim && dim == right.dim &&
arrayed == right.arrayed && arrayed == right.arrayed &&
shadow == right.shadow && shadow == right.shadow &&
ms == right.ms && ms == right.ms &&
image == right.image && image == right.image &&
combined == right.combined && combined == right.combined &&
sampler == right.sampler && sampler == right.sampler &&
external == right.external && external == right.external &&
vectorSize == right.vectorSize; vectorSize == right.vectorSize &&
structReturnIndex == right.structReturnIndex;
} }
bool operator!=(const TSampler& right) const bool operator!=(const TSampler& right) const

View File

@ -104,11 +104,20 @@ void TType::buildMangledName(TString& mangledName) const
default: break; // some compilers want this default: break; // some compilers want this
} }
switch (sampler.vectorSize) { if (sampler.hasReturnStruct()) {
case 1: mangledName += "1"; break; // Name mangle for sampler return struct uses struct table index.
case 2: mangledName += "2"; break; mangledName += "-tx-struct";
case 3: mangledName += "3"; break;
case 4: break; // default to prior name mangle behavior char text[16]; // plenty enough space for the small integers.
snprintf(text, sizeof(text), "%d-", sampler.structReturnIndex);
mangledName += text;
} else {
switch (sampler.getVectorSize()) {
case 1: mangledName += "1"; break;
case 2: mangledName += "2"; break;
case 3: mangledName += "3"; break;
case 4: break; // default to prior name mangle behavior
}
} }
if (sampler.ms) if (sampler.ms)

View File

@ -291,6 +291,7 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.structIoFourWay.frag", "main"}, {"hlsl.structIoFourWay.frag", "main"},
{"hlsl.structStructName.frag", "main"}, {"hlsl.structStructName.frag", "main"},
{"hlsl.synthesizeInput.frag", "main"}, {"hlsl.synthesizeInput.frag", "main"},
{"hlsl.texture.struct.frag", "main"},
{"hlsl.texture.subvec4.frag", "main"}, {"hlsl.texture.subvec4.frag", "main"},
{"hlsl.this.frag", "main"}, {"hlsl.this.frag", "main"},
{"hlsl.intrinsics.vert", "VertexShaderFunction"}, {"hlsl.intrinsics.vert", "VertexShaderFunction"},

View File

@ -1189,7 +1189,13 @@ bool HlslGrammar::acceptTextureType(TType& type)
const TBasicType basicRetType = txType.getBasicType() ; const TBasicType basicRetType = txType.getBasicType() ;
if (basicRetType != EbtFloat && basicRetType != EbtUint && basicRetType != EbtInt) { switch (basicRetType) {
case EbtFloat:
case EbtUint:
case EbtInt:
case EbtStruct:
break;
default:
unimplemented("basic type in texture"); unimplemented("basic type in texture");
return false; return false;
} }
@ -1206,8 +1212,8 @@ bool HlslGrammar::acceptTextureType(TType& type)
return false; return false;
} }
if (!txType.isScalar() && !txType.isVector()) { if (!txType.isScalar() && !txType.isVector() && !txType.isStruct()) {
expected("scalar or vector type"); expected("scalar, vector, or struct type");
return false; return false;
} }
@ -1244,20 +1250,24 @@ bool HlslGrammar::acceptTextureType(TType& type)
if (image || dim == EsdBuffer) if (image || dim == EsdBuffer)
format = parseContext.getLayoutFromTxType(token.loc, txType); format = parseContext.getLayoutFromTxType(token.loc, txType);
const TBasicType txBasicType = txType.isStruct() ? (*txType.getStruct())[0].type->getBasicType()
: txType.getBasicType();
// Non-image Buffers are combined // Non-image Buffers are combined
if (dim == EsdBuffer && !image) { if (dim == EsdBuffer && !image) {
sampler.set(txType.getBasicType(), dim, array); sampler.set(txType.getBasicType(), dim, array);
} else { } else {
// DX10 textures are separated. TODO: DX9. // DX10 textures are separated. TODO: DX9.
if (image) { if (image) {
sampler.setImage(txType.getBasicType(), dim, array, shadow, ms); sampler.setImage(txBasicType, dim, array, shadow, ms);
} else { } else {
sampler.setTexture(txType.getBasicType(), dim, array, shadow, ms); sampler.setTexture(txBasicType, dim, array, shadow, ms);
} }
} }
// Remember the declared vector size. // Remember the declared return type. Function returns false on error.
sampler.vectorSize = txType.getVectorSize(); if (!parseContext.setTextureReturnType(sampler, txType, token.loc))
return false;
// Force uncombined, if necessary // Force uncombined, if necessary
if (!combined) if (!combined)

View File

@ -189,7 +189,14 @@ void HlslParseContext::growGlobalUniformBlock(const TSourceLoc& loc, TType& memb
// //
TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const TType& txType) TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const TType& txType)
{ {
if (txType.isStruct()) {
// TODO: implement.
error(loc, "unimplemented: structure type in image or buffer", "", "");
return ElfNone;
}
const int components = txType.getVectorSize(); const int components = txType.getVectorSize();
const TBasicType txBasicType = txType.getBasicType();
const auto selectFormat = [this,&components](TLayoutFormat v1, TLayoutFormat v2, TLayoutFormat v4) -> TLayoutFormat { const auto selectFormat = [this,&components](TLayoutFormat v1, TLayoutFormat v2, TLayoutFormat v4) -> TLayoutFormat {
if (intermediate.getNoStorageFormat()) if (intermediate.getNoStorageFormat())
@ -199,7 +206,7 @@ TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const
components == 2 ? v2 : v4; components == 2 ? v2 : v4;
}; };
switch (txType.getBasicType()) { switch (txBasicType) {
case EbtFloat: return selectFormat(ElfR32f, ElfRg32f, ElfRgba32f); case EbtFloat: return selectFormat(ElfR32f, ElfRg32f, ElfRgba32f);
case EbtInt: return selectFormat(ElfR32i, ElfRg32i, ElfRgba32i); case EbtInt: return selectFormat(ElfR32i, ElfRg32i, ElfRgba32i);
case EbtUint: return selectFormat(ElfR32ui, ElfRg32ui, ElfRgba32ui); case EbtUint: return selectFormat(ElfR32ui, ElfRg32ui, ElfRgba32ui);
@ -370,7 +377,8 @@ TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char*
const TSampler& texSampler = object->getType().getSampler(); const TSampler& texSampler = object->getType().getSampler();
const TType objDerefType(texSampler.type, EvqTemporary, texSampler.vectorSize); TType objDerefType;
getTextureReturnType(texSampler, objDerefType);
if (nodeAsBinary) { if (nodeAsBinary) {
TIntermTyped* rhs = nodeAsBinary->getRight(); TIntermTyped* rhs = nodeAsBinary->getRight();
@ -771,7 +779,10 @@ TIntermTyped* HlslParseContext::handleBracketOperator(const TSourceLoc& loc, TIn
} else { } else {
TIntermAggregate* load = new TIntermAggregate(sampler.isImage() ? EOpImageLoad : EOpTextureFetch); TIntermAggregate* load = new TIntermAggregate(sampler.isImage() ? EOpImageLoad : EOpTextureFetch);
load->setType(TType(sampler.type, EvqTemporary, sampler.vectorSize)); TType sampReturnType;
getTextureReturnType(sampler, sampReturnType);
load->setType(sampReturnType);
load->setLoc(loc); load->setLoc(loc);
load->getSequence().push_back(base); load->getSequence().push_back(base);
load->getSequence().push_back(index); load->getSequence().push_back(index);
@ -3292,21 +3303,99 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (node == nullptr || !node->getAsOperator()) if (node == nullptr || !node->getAsOperator())
return; return;
const auto clampReturn = [&loc, &node, this](TIntermTyped* result, const TSampler& sampler) -> TIntermTyped* { // Sampler return must always be a vec4, but we can construct a shorter vector or a structure from it.
// Sampler return must always be a vec4, but we can construct a shorter vector const auto convertReturn = [&loc, &node, this](TIntermTyped* result, const TSampler& sampler) -> TIntermTyped* {
result->setType(TType(node->getType().getBasicType(), EvqTemporary, node->getVectorSize())); result->setType(TType(node->getType().getBasicType(), EvqTemporary, node->getVectorSize()));
if (sampler.vectorSize < (unsigned)node->getVectorSize()) { TIntermTyped* convertedResult = nullptr;
// Too many components. Construct shorter vector from it.
const TType clampedType(result->getType().getBasicType(), EvqTemporary, sampler.vectorSize);
const TOperator op = intermediate.mapTypeToConstructorOp(clampedType); TType retType;
getTextureReturnType(sampler, retType);
result = constructBuiltIn(clampedType, op, result, loc, false); if (retType.isStruct()) {
// For type convenience, conversionAggregate points to the convertedResult (we know it's an aggregate here)
TIntermAggregate* conversionAggregate = new TIntermAggregate;
convertedResult = conversionAggregate;
// Convert vector output to return structure. We will need a temp symbol to copy the results to.
TVariable* structVar = makeInternalVariable("@sampleStructTemp", retType);
// We also need a temp symbol to hold the result of the texture. We don't want to re-fetch the
// sample each time we'll index into the result, so we'll copy to this, and index into the copy.
TVariable* sampleShadow = makeInternalVariable("@sampleResultShadow", result->getType());
// Initial copy from texture to our sample result shadow.
TIntermTyped* shadowCopy = intermediate.addAssign(EOpAssign, intermediate.addSymbol(*sampleShadow, loc),
result, loc);
conversionAggregate->getSequence().push_back(shadowCopy);
unsigned vec4Pos = 0;
for (unsigned m = 0; m < unsigned(retType.getStruct()->size()); ++m) {
const TType memberType(retType, m); // dereferenced type of the member we're about to assign.
// Check for bad struct members. This should have been caught upstream. Complain, because
// wwe don't know what to do with it. This algorithm could be generalized to handle
// other things, e.g, sub-structures, but HLSL doesn't allow them.
if (!memberType.isVector() && !memberType.isScalar()) {
error(loc, "expected: scalar or vector type in texture structure", "", "");
return nullptr;
}
// Index into the struct variable to find the member to assign.
TIntermTyped* structMember = intermediate.addIndex(EOpIndexDirectStruct,
intermediate.addSymbol(*structVar, loc),
intermediate.addConstantUnion(m, loc), loc);
structMember->setType(memberType);
// Assign each component of (possible) vector in struct member.
for (int component = 0; component < memberType.getVectorSize(); ++component) {
TIntermTyped* vec4Member = intermediate.addIndex(EOpIndexDirect,
intermediate.addSymbol(*sampleShadow, loc),
intermediate.addConstantUnion(vec4Pos++, loc), loc);
vec4Member->setType(TType(memberType.getBasicType(), EvqTemporary, 1));
TIntermTyped* memberAssign = nullptr;
if (memberType.isVector()) {
// Vector member: we need to create an access chain to the vector component.
TIntermTyped* structVecComponent = intermediate.addIndex(EOpIndexDirect, structMember,
intermediate.addConstantUnion(component, loc), loc);
memberAssign = intermediate.addAssign(EOpAssign, structVecComponent, vec4Member, loc);
} else {
// Scalar member: we can assign to it directly.
memberAssign = intermediate.addAssign(EOpAssign, structMember, vec4Member, loc);
}
conversionAggregate->getSequence().push_back(memberAssign);
}
}
// Add completed variable so the expression results in the whole struct value we just built.
conversionAggregate->getSequence().push_back(intermediate.addSymbol(*structVar, loc));
// Make it a sequence.
intermediate.setAggregateOperator(conversionAggregate, EOpSequence, retType, loc);
} else {
// vector clamp the output if template vector type is smaller than sample result.
if (retType.getVectorSize() < node->getVectorSize()) {
// Too many components. Construct shorter vector from it.
const TOperator op = intermediate.mapTypeToConstructorOp(retType);
convertedResult = constructBuiltIn(retType, op, result, loc, false);
} else {
// Enough components. Use directly.
convertedResult = result;
}
} }
result->setLoc(loc); convertedResult->setLoc(loc);
return result; return convertedResult;
}; };
const TOperator op = node->getAsOperator()->getOp(); const TOperator op = node->getAsOperator()->getOp();
@ -3372,7 +3461,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
tex->getSequence().push_back(constructCoord); // coordinate tex->getSequence().push_back(constructCoord); // coordinate
tex->getSequence().push_back(bias); // bias tex->getSequence().push_back(bias); // bias
node = clampReturn(tex, sampler); node = convertReturn(tex, sampler);
break; break;
} }
@ -3412,7 +3501,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (argOffset != nullptr) if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset); txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler); node = convertReturn(txsample, sampler);
break; break;
} }
@ -3445,7 +3534,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (argOffset != nullptr) if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset); txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler); node = convertReturn(txsample, sampler);
break; break;
} }
@ -3721,7 +3810,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
txfetch->getSequence().push_back(argOffset); txfetch->getSequence().push_back(argOffset);
} }
node = clampReturn(txfetch, sampler); node = convertReturn(txfetch, sampler);
break; break;
} }
@ -3752,7 +3841,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (argOffset != nullptr) if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset); txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler); node = convertReturn(txsample, sampler);
break; break;
} }
@ -8756,6 +8845,106 @@ void HlslParseContext::clearUniformInputOutput(TQualifier& qualifier)
} }
// Set texture return type. Returns success (not all types are valid).
bool HlslParseContext::setTextureReturnType(TSampler& sampler, const TType& retType, const TSourceLoc& loc)
{
// Seed the output with an invalid index. We will set it to a valid one if we can.
sampler.structReturnIndex = TSampler::noReturnStruct;
// Arrays aren't supported.
if (retType.isArray()) {
error(loc, "Arrays not supported in texture template types", "", "");
return false;
}
// If return type is a vector, remember the vector size in the sampler, and return.
if (retType.isVector() || retType.isScalar()) {
sampler.vectorSize = retType.getVectorSize();
return true;
}
// If it wasn't a vector, it must be a struct meeting certain requirements. The requirements
// are checked below: just check for struct-ness here.
if (!retType.isStruct()) {
error(loc, "Invalid texture template type", "", "");
return false;
}
TTypeList* members = retType.getWritableStruct();
// Check for too many or not enough structure members.
if (members->size() > 4 || members->size() == 0) {
error(loc, "Invalid member count in texture template structure", "", "");
return false;
}
// Error checking: We must have <= 4 total components, all of the same basic type.
unsigned totalComponents = 0;
for (unsigned m = 0; m < members->size(); ++m) {
// Check for bad member types
if (!(*members)[m].type->isScalar() && !(*members)[m].type->isVector()) {
error(loc, "Invalid texture template struct member type", "", "");
return false;
}
const unsigned memberVectorSize = (*members)[m].type->getVectorSize();
totalComponents += memberVectorSize;
// too many total member components
if (totalComponents > 4) {
error(loc, "Too many components in texture template structure type", "", "");
return false;
}
// All members must be of a common basic type
if ((*members)[m].type->getBasicType() != (*members)[0].type->getBasicType()) {
error(loc, "Texture template structure members must same basic type", "", "");
return false;
}
}
// If the structure in the return type already exists in the table, we'll use it. Otherwise, we'll make
// a new entry. This is a linear search, but it hardly ever happens, and the list cannot be very large.
for (unsigned int idx = 0; idx < textureReturnStruct.size(); ++idx) {
if (textureReturnStruct[idx] == members) {
sampler.structReturnIndex = idx;
return true;
}
}
// It wasn't found as an existing entry. See if we have room for a new one.
if (textureReturnStruct.size() >= TSampler::structReturnSlots) {
error(loc, "Texture template struct return slots exceeded", "", "");
return false;
}
// Insert it in the vector that tracks struct return types.
sampler.structReturnIndex = unsigned(textureReturnStruct.size());
textureReturnStruct.push_back(members);
// Success!
return true;
}
// Return the sampler return type in retType.
void HlslParseContext::getTextureReturnType(const TSampler& sampler, TType& retType) const
{
if (sampler.hasReturnStruct()) {
assert(textureReturnStruct.size() >= sampler.structReturnIndex);
// We land here if the texture return is a structure.
TTypeList* blockStruct = textureReturnStruct[sampler.structReturnIndex];
const TType resultType(blockStruct, "");
retType.shallowCopy(resultType);
} else {
// We land here if the texture return is a vector or scalar.
const TType resultType(sampler.type, EvqTemporary, sampler.getVectorSize());
retType.shallowCopy(resultType);
}
}
// Return a symbol for the tessellation linkage variable of the given TBuiltInVariable type // Return a symbol for the tessellation linkage variable of the given TBuiltInVariable type
TIntermSymbol* HlslParseContext::findTessLinkageSymbol(TBuiltInVariable biType) const TIntermSymbol* HlslParseContext::findTessLinkageSymbol(TBuiltInVariable biType) const
{ {

View File

@ -213,6 +213,12 @@ public:
// Share struct buffer deep types // Share struct buffer deep types
void shareStructBufferType(TType&); void shareStructBufferType(TType&);
// Set texture return type of the given sampler. Returns success (not all types are valid).
bool setTextureReturnType(TSampler& sampler, const TType& retType, const TSourceLoc& loc);
// Obtain the sampler return type of the given sampler in retType.
void getTextureReturnType(const TSampler& sampler, TType& retType) const;
protected: protected:
struct TFlattenData { struct TFlattenData {
TFlattenData() : nextBinding(TQualifier::layoutBindingEnd), TFlattenData() : nextBinding(TQualifier::layoutBindingEnd),
@ -389,6 +395,10 @@ protected:
// Structuredbuffer shared types. Typically there are only a few. // Structuredbuffer shared types. Typically there are only a few.
TVector<TType*> structBufferTypes; TVector<TType*> structBufferTypes;
// This tracks texture sample user structure return types. Only a limited number are supported, as
// may fit in TSampler::structReturnIndex.
TVector<TTypeList*> textureReturnStruct;
TMap<TString, bool> structBufferCounter; TMap<TString, bool> structBufferCounter;
// The built-in interstage IO map considers e.g, EvqPosition on input and output separately, so that we // The built-in interstage IO map considers e.g, EvqPosition on input and output separately, so that we