Improve handling of multi-dimensional array input/outputs from shaders

This commit is contained in:
baldurk
2020-02-03 13:14:45 +00:00
parent 850ff5b65d
commit a98a8c49a7
10 changed files with 406 additions and 69 deletions
+1 -6
View File
@@ -603,7 +603,7 @@ struct SigParameter
regIndex == o.regIndex && systemValue == o.systemValue && compType == o.compType &&
regChannelMask == o.regChannelMask && channelUsedMask == o.channelUsedMask &&
needSemanticIndex == o.needSemanticIndex && compCount == o.compCount &&
stream == o.stream && arrayIndex == o.arrayIndex;
stream == o.stream;
}
bool operator<(const SigParameter &o) const
{
@@ -631,8 +631,6 @@ struct SigParameter
return compCount < o.compCount;
if(!(stream == o.stream))
return stream < o.stream;
if(!(arrayIndex == o.arrayIndex))
return arrayIndex < o.arrayIndex;
return false;
}
@@ -675,9 +673,6 @@ shader itself, for APIs that pack signatures together.
"Selects a stream for APIs that provide multiple output streams for the same named output.");
uint32_t stream = 0;
DOCUMENT("If this element is part of an array, indicates the index, or :data:`NoIndex` if not.");
uint32_t arrayIndex = ~0U;
static const uint32_t NoIndex = ~0U;
};
+293 -8
View File
@@ -1428,7 +1428,7 @@ void main() {
}
};
SECTION("matrix and array outputs")
SECTION("matrix and 1D array outputs")
{
rdcstr source = R"(
#version 450 core
@@ -1480,7 +1480,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 0);
CHECK(sig.arrayIndex == 0);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 3);
@@ -1494,7 +1493,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 1);
CHECK(sig.arrayIndex == 1);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 3);
@@ -1508,7 +1506,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 2);
CHECK(sig.arrayIndex == 2);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 3);
@@ -1548,7 +1545,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 9);
CHECK(sig.arrayIndex == 0);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 2);
@@ -1562,7 +1558,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 10);
CHECK(sig.arrayIndex == 0);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 2);
@@ -1576,7 +1571,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 11);
CHECK(sig.arrayIndex == 1);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 2);
@@ -1590,7 +1584,6 @@ void main()
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 12);
CHECK(sig.arrayIndex == 1);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 2);
@@ -1600,6 +1593,298 @@ void main()
}
};
// this is an annoying one. We want to specify a location explicitly to be GL/SPIR-V compatible,
// but on GL if we specify a location the location assignment handling breaks. Since we only
// need to handle this for tests (real drivers will let us query the locations when needed) AND
// it's an extremely obtuse scenario, we just let GL have no location
const rdcstr locDefine = (testType == ShaderType::GLSPIRV || testType == ShaderType::Vulkan)
? "#define LOC(l) layout(location = l)"
: "#define LOC(l)";
SECTION("nested struct/array inputs/outputs")
{
rdcstr source = R"(
#version 450 core
)" + locDefine + R"(
struct leaf
{
float x;
};
struct nest
{
float a[2];
leaf b[2];
};
struct base
{
float a;
vec3 b;
nest c[2];
};
layout(binding = 0, std140) uniform ubo_block {
base inB;
} ubo_root;
LOC(0) out base outB;
void main()
{
gl_Position = vec4(0, 0, 0, 1);
outB = ubo_root.inB;
}
)";
ShaderReflection refl;
ShaderBindpointMapping mapping;
compile(ShaderStage::Vertex, source, "main", refl, mapping);
REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 11);
{
CHECK(refl.outputSignature[0].varName.contains("gl_Position"));
{
const SigParameter &sig = refl.outputSignature[0];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 0);
CHECK(sig.systemValue == ShaderBuiltin::Position);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 4);
CHECK(sig.regChannelMask == 0xf);
CHECK(sig.channelUsedMask == 0xf);
}
CHECK(refl.outputSignature[1].varName == "outB.a");
{
const SigParameter &sig = refl.outputSignature[1];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 0);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[2].varName == "outB.b");
{
const SigParameter &sig = refl.outputSignature[2];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 1);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 3);
CHECK(sig.regChannelMask == 0x7);
CHECK(sig.channelUsedMask == 0x7);
}
CHECK(refl.outputSignature[3].varName == "outB.c[0].a[0]");
{
const SigParameter &sig = refl.outputSignature[3];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 2);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[4].varName == "outB.c[0].a[1]");
{
const SigParameter &sig = refl.outputSignature[4];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 3);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[5].varName == "outB.c[0].b[0].x");
{
const SigParameter &sig = refl.outputSignature[5];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 4);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[6].varName == "outB.c[0].b[1].x");
{
const SigParameter &sig = refl.outputSignature[6];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 5);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[7].varName == "outB.c[1].a[0]");
{
const SigParameter &sig = refl.outputSignature[7];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 6);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[8].varName == "outB.c[1].a[1]");
{
const SigParameter &sig = refl.outputSignature[8];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 7);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[9].varName == "outB.c[1].b[0].x");
{
const SigParameter &sig = refl.outputSignature[9];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 8);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
CHECK(refl.outputSignature[10].varName == "outB.c[1].b[1].x");
{
const SigParameter &sig = refl.outputSignature[10];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 9);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 1);
CHECK(sig.regChannelMask == 0x1);
CHECK(sig.channelUsedMask == 0x1);
}
}
}
SECTION("multi-dimensional array inputs/outputs")
{
rdcstr source = R"(
#version 450 core
)" + locDefine + R"(
LOC(0) in vec3 inarr[1][3][2];
LOC(0) out vec3 outarr[1][3][2];
void main()
{
gl_Position = vec4(0, 0, 0, 1);
for(int i=0; i < 1*3*2; i++)
outarr[(i/6)][(i/2)%3][i%2] = inarr[(i/6)][(i/2)%3][i%2];
}
)";
ShaderReflection refl;
ShaderBindpointMapping mapping;
compile(ShaderStage::Vertex, source, "main", refl, mapping);
REQUIRE_ARRAY_SIZE(refl.samplers.size(), 0);
REQUIRE_ARRAY_SIZE(refl.constantBlocks.size(), 0);
REQUIRE_ARRAY_SIZE(refl.readOnlyResources.size(), 0);
REQUIRE_ARRAY_SIZE(refl.readWriteResources.size(), 0);
REQUIRE(refl.inputSignature.size() >= 6);
// glslang will insert gl_VertexID and gl_InstanceID here in SPIR-V compilation
CHECK((refl.inputSignature.size() == 6 || refl.inputSignature.size() == 8));
REQUIRE_ARRAY_SIZE(refl.outputSignature.size(), 7);
for(size_t i = 0; i < 2; i++)
{
const rdcarray<SigParameter> &sigarray = (i == 0) ? refl.inputSignature : refl.outputSignature;
size_t idx = 0;
if(i == 0)
{
if(sigarray[0].varName.contains("gl_VertexID"))
{
// skip without checking
idx++;
}
if(sigarray[1].varName.contains("gl_InstanceID"))
{
// skip without checking
idx++;
}
}
else if(i == 1)
{
CHECK(sigarray[0].varName.contains("gl_Position"));
{
const SigParameter &sig = sigarray[0];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == 0);
CHECK(sig.systemValue == ShaderBuiltin::Position);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 4);
CHECK(sig.regChannelMask == 0xf);
CHECK(sig.channelUsedMask == 0xf);
}
idx++;
}
for(uint32_t a = 0; a < 6; a++)
{
rdcstr expectedName = StringFormat::Fmt("%sarr[%d][%d][%d]", i == 0 ? "in" : "out", a / 6,
(a / 2) % 3, (a % 2));
CHECK(sigarray[idx].varName == expectedName);
{
const SigParameter &sig = sigarray[idx];
INFO("signature element: " << sig.varName.c_str());
CHECK(sig.regIndex == a);
CHECK(sig.systemValue == ShaderBuiltin::Undefined);
CHECK(sig.compType == CompType::Float);
CHECK(sig.compCount == 3);
CHECK(sig.regChannelMask == 0x7);
CHECK(sig.channelUsedMask == 0x7);
}
idx++;
}
}
};
SECTION("shader input/output blocks")
{
rdcstr source = R"(
-8
View File
@@ -1086,10 +1086,7 @@ static void AddSigParameter(rdcarray<SigParameter> &sigs, uint32_t &regIndex,
s.regIndex += arrayIdx;
if(arrayIdx >= 0)
{
s.arrayIndex = arrayIdx;
s.varName = StringFormat::Fmt("%s[%d]", nm, arrayIdx);
}
sigs.push_back(s);
}
@@ -1107,14 +1104,9 @@ static void AddSigParameter(rdcarray<SigParameter> &sigs, uint32_t &regIndex,
s.regIndex += r;
if(arrayIdx >= 0)
{
s.arrayIndex = arrayIdx;
s.varName = StringFormat::Fmt("%s[%d]:row%d", nm, arrayIdx, r);
}
else
{
s.varName = StringFormat::Fmt("%s:row%d", nm, r);
}
sigs.push_back(s);
}
+3 -2
View File
@@ -2497,7 +2497,7 @@ GLuint APIENTRY _glGetProgramResourceIndex(GLuint program, GLenum programInterfa
return 0;
}
return glslangGetProgramResourceIndex(glslangProgram, name);
return glslangGetProgramResourceIndex(glslangProgram, ConvertInterface(programInterface), name);
}
void APIENTRY _glGetProgramResourceName(GLuint program, GLenum programInterface, GLuint index,
@@ -3483,7 +3483,8 @@ void APIENTRY _testStub_GetActiveUniformBlockiv(GLuint program, GLuint uniformBl
GLint APIENTRY _testStub_AttribLocation(GLuint program, const GLchar *name)
{
GLuint index = GL.glGetProgramResourceIndex(program, eGL_UNIFORM_BLOCK, name);
GLuint index = GL.glGetProgramResourceIndex(program, eGL_PROGRAM_INPUT, name);
RDCASSERT(index != GL_INVALID_INDEX);
GLenum prop = eGL_LOCATION;
GLint value = -1;
@@ -610,21 +610,59 @@ void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface
}
}
uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program, const char *name)
uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program,
ReflectionInterface programInterface, const char *name)
{
uint32_t idx = program->getReflectionIndex(name);
rdcstr n = name;
// Additionally, if <name> would exactly match the name string of an active
// resource if "[0]" were appended to <name>, the index of the matched
// resource is returned.
if(idx == ~0U)
for(int pass = 0; pass < 2; pass++)
{
rdcstr arraysuffixed = name;
arraysuffixed += "[0]";
idx = program->getReflectionIndex(arraysuffixed.c_str());
// glslang namespaces aggregates that it blows up with our reflection settings, assuming we
// don't get an exact match for the name try with the appropriate prefix for this interface
if(pass == 1 && programInterface == ReflectionInterface::Input)
n = "in " + n;
else if(pass == 1 && programInterface == ReflectionInterface::Output)
n = "out " + n;
else if(pass == 1)
break;
uint32_t idx = program->getReflectionIndex(n.c_str());
// Additionally, if <name> would exactly match the name string of an active
// resource if "[0]" were appended to <name>, the index of the matched
// resource is returned.
if(idx == ~0U)
{
rdcstr arraysuffixed = n;
arraysuffixed += "[0]";
idx = program->getReflectionIndex(arraysuffixed.c_str());
}
// for I/O inputs, if the name ended in an array index, try and subtract that, query for the
// name with [0].
if((programInterface == ReflectionInterface::Input ||
programInterface == ReflectionInterface::Output) &&
idx == ~0U && n.back() == ']')
{
rdcstr unsuffixed = n;
unsuffixed.pop_back();
while(unsuffixed.back() >= '0' && unsuffixed.back() <= '9')
unsuffixed.pop_back();
if(unsuffixed.back() == '[')
{
unsuffixed.pop_back();
unsuffixed += "[0]";
idx = program->getReflectionIndex(unsuffixed.c_str());
}
}
if(idx != ~0U)
return idx;
}
return idx;
return ~0U;
}
const char *glslangGetProgramResourceName(glslang::TProgram *program,
@@ -85,7 +85,8 @@ void glslangGetProgramInterfaceiv(glslang::TProgram *program, ReflectionInterfac
void glslangGetProgramResourceiv(glslang::TProgram *program, ReflectionInterface programInterface,
uint32_t index, const rdcarray<ReflectionProperty> &props,
int32_t bufSize, int32_t *length, int32_t *params);
uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program, const char *name);
uint32_t glslangGetProgramResourceIndex(glslang::TProgram *program,
ReflectionInterface programInterface, const char *name);
const char *glslangGetProgramResourceName(glslang::TProgram *program,
ReflectionInterface programInterface, uint32_t index);
@@ -2100,32 +2100,29 @@ void Reflector::AddSignatureParameter(const bool isInput, const ShaderStage stag
isArray = true;
varType = &dataTypes[varType->InnerType()];
// for geometry/tessellation evaluation shaders, ignore the root level of array-ness for inputs
if((stage == ShaderStage::Geometry || stage == ShaderStage::Tess_Eval) && isInput &&
parentStructID == 0)
arraySize = 1;
// for tessellation control shaders, ignore the root level of array-ness for both inputs and
// outputs
if(stage == ShaderStage::Tess_Control && parentStructID == 0)
arraySize = 1;
// step through multi-dimensional arrays
while(varType->type == DataType::ArrayType)
varType = &dataTypes[varType->InnerType()];
// if this is a root array in the geometry shader, don't reflect it as an array
if(stage == ShaderStage::Geometry && isInput && parentStructID == 0)
// if this is the first array level, we sometimes ignore it.
if(patch.accessChain.empty())
{
arraySize = 1;
isArray = false;
// for geometry/tessellation evaluation shaders, ignore the root level of array-ness for
// inputs
if((stage == ShaderStage::Geometry || stage == ShaderStage::Tess_Eval) && isInput)
arraySize = 1;
// for tessellation control shaders, ignore the root level of array-ness for both inputs and
// outputs
if(stage == ShaderStage::Tess_Control)
arraySize = 1;
// if this is a root array in the geometry shader, don't reflect it as an array either
if(stage == ShaderStage::Geometry && isInput)
isArray = false;
}
// arrays will need an extra access chain index
patch.accessChain.push_back(0U);
}
// arrays will need an extra access chain index
if(isArray)
patch.accessChain.push_back(0U);
// if the current type is a struct, recurse for each member
if(varType->type == DataType::StructType)
{
for(uint32_t a = 0; a < arraySize; a++)
@@ -2167,6 +2164,23 @@ void Reflector::AddSignatureParameter(const bool isInput, const ShaderStage stag
return;
}
// similarly for arrays (this happens for multi-dimensional arrays
if(varType->type == DataType::ArrayType)
{
for(uint32_t a = 0; a < arraySize; a++)
{
AddSignatureParameter(isInput, stage, globalID, varType->id, regIndex, patch,
varName + StringFormat::Fmt("[%u]", a), *varType, {}, sigarray,
patchData, specInfo);
// increment the array-index access chain value
patch.accessChain.back()++;
patch.isArraySubsequentElement = true;
}
return;
}
switch(varType->scalar().type)
{
case Op::TypeBool:
@@ -2191,10 +2205,7 @@ void Reflector::AddSignatureParameter(const bool isInput, const ShaderStage stag
rdcstr n = varName;
if(isArray)
{
n += StringFormat::Fmt("[%u]", a);
sig.arrayIndex = a;
}
sig.varName = n;
+1 -2
View File
@@ -165,9 +165,8 @@ void DoSerialise(SerialiserType &ser, SigParameter &el)
SERIALISE_MEMBER(needSemanticIndex);
SERIALISE_MEMBER(compCount);
SERIALISE_MEMBER(stream);
SERIALISE_MEMBER(arrayIndex);
SIZE_CHECK(104);
SIZE_CHECK(96);
}
template <typename SerialiserType>
+8 -4
View File
@@ -182,7 +182,7 @@ struct SimpleWrapper
struct MyStruct
{
float a;
float b[2];
float b[2][3];
ArrayWrapper c;
SimpleWrapper d[2];
};
@@ -202,12 +202,16 @@ void main()
OutDummy = vec4(0,0,0,0);
outData.outStruct.a = 1.1f;
outData.outStruct.b[0] = 2.2f;
outData.outStruct.b[1] = 3.3f;
outData.outStruct.c.foo[0] = 4.4f;
outData.outStruct.c.foo[1] = 5.5f;
outData.outStruct.d[0].foo = 6.6f;
outData.outStruct.d[1].foo = 7.7f;
outData.outStruct.b[0][0] = 2.2f;
outData.outStruct.b[0][1] = 3.3f;
outData.outStruct.b[0][2] = 8.8f;
outData.outStruct.b[1][0] = 9.9f;
outData.outStruct.b[1][1] = 9.1f;
outData.outStruct.b[1][2] = 8.2f;
}
)EOSHADER";
@@ -233,7 +237,7 @@ struct SimpleWrapper
struct MyStruct
{
float a;
float b[2];
float b[2][3];
ArrayWrapper c;
SimpleWrapper d[2];
};
+14 -3
View File
@@ -103,12 +103,16 @@ class VK_Vertex_Attr_Zoo(rdtest.TestCase):
ref = {
0: {
'outData.outStruct.a': [1.1],
'outData.outStruct.b[0]': [2.2],
'outData.outStruct.b[1]': [3.3],
'outData.outStruct.c.foo[0]': [4.4],
'outData.outStruct.c.foo[1]': [5.5],
'outData.outStruct.d[0].foo': [6.6],
'outData.outStruct.d[1].foo': [7.7],
'outData.outStruct.b[0][0]': [2.2],
'outData.outStruct.b[0][1]': [3.3],
'outData.outStruct.b[0][2]': [8.8],
'outData.outStruct.b[1][0]': [9.9],
'outData.outStruct.b[1][1]': [9.1],
'outData.outStruct.b[1][2]': [8.2],
},
}
@@ -116,7 +120,14 @@ class VK_Vertex_Attr_Zoo(rdtest.TestCase):
rdtest.log.success("Nested vertex output data is as expected")
# The array-of-structs data is a broken in transform feedback
# The array-of-structs or array-of-arrays data is a broken in transform feedback
del ref[0]['outData.outStruct.b[0][0]']
del ref[0]['outData.outStruct.b[0][1]']
del ref[0]['outData.outStruct.b[0][2]']
del ref[0]['outData.outStruct.b[1][0]']
del ref[0]['outData.outStruct.b[1][1]']
del ref[0]['outData.outStruct.b[1][2]']
del ref[0]['outData.outStruct.d[0].foo']
del ref[0]['outData.outStruct.d[1].foo']