HLSL: add methods to track user structure in texture return type.

Some languages allow a restricted set of user structure types returned from texture sampling
operations.  Restrictions include the total vector size of all components may not exceed 4,
and the basic types of all members must be identical.

This adds underpinnings for that ability.  Because storing a whole TType or even a simple
TTypeList in the TSampler would be expensive, the structure definition is held in a
table outside the TType.  The TSampler contains a small bitfield index, currently 4 bits
to support up to 15 separate texture template structure types, but that can be adjusted
up or down.  Vector returns are handled as before.

There are abstraction methods accepting and returning a TType (such as may have been parsed
from a grammar).  The new methods will accept a texture template type and set the
sampler to the structure if possible, checking a range of error conditions such as whether
the total structure vector components exceed 4, or whether their basic types differe, or
whether the struct contains non-vector-or-scalar members.  Another query returns the
appropriate TType for the sampler.

High level summary of design:

In the TSampler, this holds an index into the texture structure return type table:

    unsigned int structReturnIndex : structReturnIndexBits;

These are the methods to set or get the return type from the TSampler.  They work for vector or structure returns, and potentially could be expanded to handle other things (small arrays?) if ever needed.

    bool setTextureReturnType(TSampler& sampler, const TType& retType, const TSourceLoc& loc);
    void getTextureReturnType(const TSampler& sampler, const TType& retType, const TSourceLoc& loc) const;

The ``convertReturn`` lambda in ``HlslParseContext::decomposeSampleMethods`` is greatly expanded to know how to copy a vec4 sample return to whatever the structure type should be.  This is a little awkward since it involves introducing a comma expression to return the proper aggregate value after a set of memberwise copies.
This commit is contained in:
LoopDawg 2017-07-31 13:41:42 -06:00
parent 03e63fa805
commit 5ee05891cf
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;
float4 main()
float4 main() : SV_Target0
{
uint MipLevel;
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 sampler : 1; // true means a pure sampler, other fields should be clear()
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 isSubpass() const { return dim == EsdSubpass; }
@ -90,6 +102,7 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
bool isShadow() const { return shadow; }
bool isArrayed() const { return arrayed; }
bool isMultiSample() const { return ms; }
bool hasReturnStruct() const { return structReturnIndex != noReturnStruct; }
void clear()
{
@ -102,6 +115,9 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
combined = false;
sampler = false;
external = false;
structReturnIndex = noReturnStruct;
// by default, returns a single vec4;
vectorSize = 4;
}
@ -160,16 +176,17 @@ struct TSampler { // misnomer now; includes images, textures without sampler,
bool operator==(const TSampler& right) const
{
return type == right.type &&
dim == right.dim &&
arrayed == right.arrayed &&
shadow == right.shadow &&
ms == right.ms &&
image == right.image &&
combined == right.combined &&
sampler == right.sampler &&
external == right.external &&
vectorSize == right.vectorSize;
return type == right.type &&
dim == right.dim &&
arrayed == right.arrayed &&
shadow == right.shadow &&
ms == right.ms &&
image == right.image &&
combined == right.combined &&
sampler == right.sampler &&
external == right.external &&
vectorSize == right.vectorSize &&
structReturnIndex == right.structReturnIndex;
}
bool operator!=(const TSampler& right) const

View File

@ -104,11 +104,20 @@ void TType::buildMangledName(TString& mangledName) const
default: break; // some compilers want this
}
switch (sampler.vectorSize) {
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.hasReturnStruct()) {
// Name mangle for sampler return struct uses struct table index.
mangledName += "-tx-struct";
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)

View File

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

View File

@ -1189,7 +1189,13 @@ bool HlslGrammar::acceptTextureType(TType& type)
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");
return false;
}
@ -1206,8 +1212,8 @@ bool HlslGrammar::acceptTextureType(TType& type)
return false;
}
if (!txType.isScalar() && !txType.isVector()) {
expected("scalar or vector type");
if (!txType.isScalar() && !txType.isVector() && !txType.isStruct()) {
expected("scalar, vector, or struct type");
return false;
}
@ -1244,20 +1250,24 @@ bool HlslGrammar::acceptTextureType(TType& type)
if (image || dim == EsdBuffer)
format = parseContext.getLayoutFromTxType(token.loc, txType);
const TBasicType txBasicType = txType.isStruct() ? (*txType.getStruct())[0].type->getBasicType()
: txType.getBasicType();
// Non-image Buffers are combined
if (dim == EsdBuffer && !image) {
sampler.set(txType.getBasicType(), dim, array);
} else {
// DX10 textures are separated. TODO: DX9.
if (image) {
sampler.setImage(txType.getBasicType(), dim, array, shadow, ms);
sampler.setImage(txBasicType, dim, array, shadow, ms);
} else {
sampler.setTexture(txType.getBasicType(), dim, array, shadow, ms);
sampler.setTexture(txBasicType, dim, array, shadow, ms);
}
}
// Remember the declared vector size.
sampler.vectorSize = txType.getVectorSize();
// Remember the declared return type. Function returns false on error.
if (!parseContext.setTextureReturnType(sampler, txType, token.loc))
return false;
// Force uncombined, if necessary
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)
{
if (txType.isStruct()) {
// TODO: implement.
error(loc, "unimplemented: structure type in image or buffer", "", "");
return ElfNone;
}
const int components = txType.getVectorSize();
const TBasicType txBasicType = txType.getBasicType();
const auto selectFormat = [this,&components](TLayoutFormat v1, TLayoutFormat v2, TLayoutFormat v4) -> TLayoutFormat {
if (intermediate.getNoStorageFormat())
@ -199,7 +206,7 @@ TLayoutFormat HlslParseContext::getLayoutFromTxType(const TSourceLoc& loc, const
components == 2 ? v2 : v4;
};
switch (txType.getBasicType()) {
switch (txBasicType) {
case EbtFloat: return selectFormat(ElfR32f, ElfRg32f, ElfRgba32f);
case EbtInt: return selectFormat(ElfR32i, ElfRg32i, ElfRgba32i);
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 TType objDerefType(texSampler.type, EvqTemporary, texSampler.vectorSize);
TType objDerefType;
getTextureReturnType(texSampler, objDerefType);
if (nodeAsBinary) {
TIntermTyped* rhs = nodeAsBinary->getRight();
@ -771,7 +779,10 @@ TIntermTyped* HlslParseContext::handleBracketOperator(const TSourceLoc& loc, TIn
} else {
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->getSequence().push_back(base);
load->getSequence().push_back(index);
@ -3292,21 +3303,99 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (node == nullptr || !node->getAsOperator())
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
// Sampler return must always be a vec4, but we can construct a shorter vector or a structure from it.
const auto convertReturn = [&loc, &node, this](TIntermTyped* result, const TSampler& sampler) -> TIntermTyped* {
result->setType(TType(node->getType().getBasicType(), EvqTemporary, node->getVectorSize()));
if (sampler.vectorSize < (unsigned)node->getVectorSize()) {
// Too many components. Construct shorter vector from it.
const TType clampedType(result->getType().getBasicType(), EvqTemporary, sampler.vectorSize);
TIntermTyped* convertedResult = nullptr;
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);
return result;
convertedResult->setLoc(loc);
return convertedResult;
};
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(bias); // bias
node = clampReturn(tex, sampler);
node = convertReturn(tex, sampler);
break;
}
@ -3412,7 +3501,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler);
node = convertReturn(txsample, sampler);
break;
}
@ -3445,7 +3534,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler);
node = convertReturn(txsample, sampler);
break;
}
@ -3721,7 +3810,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
txfetch->getSequence().push_back(argOffset);
}
node = clampReturn(txfetch, sampler);
node = convertReturn(txfetch, sampler);
break;
}
@ -3752,7 +3841,7 @@ void HlslParseContext::decomposeSampleMethods(const TSourceLoc& loc, TIntermType
if (argOffset != nullptr)
txsample->getSequence().push_back(argOffset);
node = clampReturn(txsample, sampler);
node = convertReturn(txsample, sampler);
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
TIntermSymbol* HlslParseContext::findTessLinkageSymbol(TBuiltInVariable biType) const
{

View File

@ -213,6 +213,12 @@ public:
// Share struct buffer deep types
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:
struct TFlattenData {
TFlattenData() : nextBinding(TQualifier::layoutBindingEnd),
@ -389,6 +395,10 @@ protected:
// Structuredbuffer shared types. Typically there are only a few.
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;
// The built-in interstage IO map considers e.g, EvqPosition on input and output separately, so that we