in renderdoc/driver/gl/gl_shader_refl.cpp [551:1061]
void ReconstructVarTree(GLenum query, GLuint sepProg, GLuint varIdx, GLint numParentBlocks,
rdcarray<ShaderConstant> *parentBlocks,
rdcarray<ShaderConstant> *defaultBlock)
{
const size_t numProps = 9;
GLenum resProps[numProps] = {eGL_TYPE, eGL_NAME_LENGTH, eGL_LOCATION,
eGL_BLOCK_INDEX, eGL_ARRAY_SIZE, eGL_OFFSET,
eGL_IS_ROW_MAJOR, eGL_ARRAY_STRIDE, eGL_MATRIX_STRIDE};
// GL_LOCATION not valid for buffer variables (it's only used if offset comes back -1, which will
// never happen for buffer variables)
if(query == eGL_BUFFER_VARIABLE)
resProps[2] = eGL_OFFSET;
GLint values[numProps] = {-1, -1, -1, -1, -1, -1, -1, -1};
GL.glGetProgramResourceiv(sepProg, query, varIdx, numProps, resProps, numProps, NULL, values);
ShaderConstant var;
var.type.elements = RDCMAX(1, values[4]);
// set type (or bail if it's not a variable - sampler or such)
switch(values[0])
{
case eGL_FLOAT_VEC4:
case eGL_FLOAT_VEC3:
case eGL_FLOAT_VEC2:
case eGL_FLOAT:
case eGL_FLOAT_MAT4:
case eGL_FLOAT_MAT3:
case eGL_FLOAT_MAT2:
case eGL_FLOAT_MAT4x2:
case eGL_FLOAT_MAT4x3:
case eGL_FLOAT_MAT3x4:
case eGL_FLOAT_MAT3x2:
case eGL_FLOAT_MAT2x4:
case eGL_FLOAT_MAT2x3: var.type.baseType = VarType::Float; break;
case eGL_DOUBLE_VEC4:
case eGL_DOUBLE_VEC3:
case eGL_DOUBLE_VEC2:
case eGL_DOUBLE:
case eGL_DOUBLE_MAT4:
case eGL_DOUBLE_MAT3:
case eGL_DOUBLE_MAT2:
case eGL_DOUBLE_MAT4x2:
case eGL_DOUBLE_MAT4x3:
case eGL_DOUBLE_MAT3x4:
case eGL_DOUBLE_MAT3x2:
case eGL_DOUBLE_MAT2x4:
case eGL_DOUBLE_MAT2x3: var.type.baseType = VarType::Double; break;
case eGL_UNSIGNED_INT_VEC4:
case eGL_UNSIGNED_INT_VEC3:
case eGL_UNSIGNED_INT_VEC2:
case eGL_UNSIGNED_INT:
case eGL_BOOL_VEC4:
case eGL_BOOL_VEC3:
case eGL_BOOL_VEC2:
case eGL_BOOL: var.type.baseType = VarType::UInt; break;
case eGL_INT_VEC4:
case eGL_INT_VEC3:
case eGL_INT_VEC2:
case eGL_INT: var.type.baseType = VarType::SInt; break;
default:
// not a variable (sampler etc)
return;
}
// set # rows if it's a matrix
var.type.rows = 1;
switch(values[0])
{
case eGL_FLOAT_MAT4:
case eGL_DOUBLE_MAT4:
case eGL_FLOAT_MAT2x4:
case eGL_DOUBLE_MAT2x4:
case eGL_FLOAT_MAT3x4:
case eGL_DOUBLE_MAT3x4: var.type.rows = 4; break;
case eGL_FLOAT_MAT3:
case eGL_DOUBLE_MAT3:
case eGL_FLOAT_MAT4x3:
case eGL_DOUBLE_MAT4x3:
case eGL_FLOAT_MAT2x3:
case eGL_DOUBLE_MAT2x3: var.type.rows = 3; break;
case eGL_FLOAT_MAT2:
case eGL_DOUBLE_MAT2:
case eGL_FLOAT_MAT4x2:
case eGL_DOUBLE_MAT4x2:
case eGL_FLOAT_MAT3x2:
case eGL_DOUBLE_MAT3x2: var.type.rows = 2; break;
default: break;
}
// set # columns
switch(values[0])
{
case eGL_FLOAT_VEC4:
case eGL_FLOAT_MAT4:
case eGL_FLOAT_MAT4x2:
case eGL_FLOAT_MAT4x3:
case eGL_DOUBLE_VEC4:
case eGL_DOUBLE_MAT4:
case eGL_DOUBLE_MAT4x2:
case eGL_DOUBLE_MAT4x3:
case eGL_UNSIGNED_INT_VEC4:
case eGL_BOOL_VEC4:
case eGL_INT_VEC4: var.type.columns = 4; break;
case eGL_FLOAT_VEC3:
case eGL_FLOAT_MAT3:
case eGL_FLOAT_MAT3x4:
case eGL_FLOAT_MAT3x2:
case eGL_DOUBLE_VEC3:
case eGL_DOUBLE_MAT3:
case eGL_DOUBLE_MAT3x4:
case eGL_DOUBLE_MAT3x2:
case eGL_UNSIGNED_INT_VEC3:
case eGL_BOOL_VEC3:
case eGL_INT_VEC3: var.type.columns = 3; break;
case eGL_FLOAT_VEC2:
case eGL_FLOAT_MAT2:
case eGL_FLOAT_MAT2x4:
case eGL_FLOAT_MAT2x3:
case eGL_DOUBLE_VEC2:
case eGL_DOUBLE_MAT2:
case eGL_DOUBLE_MAT2x4:
case eGL_DOUBLE_MAT2x3:
case eGL_UNSIGNED_INT_VEC2:
case eGL_BOOL_VEC2:
case eGL_INT_VEC2: var.type.columns = 2; break;
case eGL_FLOAT:
case eGL_DOUBLE:
case eGL_UNSIGNED_INT:
case eGL_INT:
case eGL_BOOL: var.type.columns = 1; break;
default: break;
}
// set name
switch(values[0])
{
case eGL_FLOAT_VEC4: var.type.name = "vec4"; break;
case eGL_FLOAT_VEC3: var.type.name = "vec3"; break;
case eGL_FLOAT_VEC2: var.type.name = "vec2"; break;
case eGL_FLOAT: var.type.name = "float"; break;
case eGL_FLOAT_MAT4: var.type.name = "mat4"; break;
case eGL_FLOAT_MAT3: var.type.name = "mat3"; break;
case eGL_FLOAT_MAT2: var.type.name = "mat2"; break;
case eGL_FLOAT_MAT4x2: var.type.name = "mat4x2"; break;
case eGL_FLOAT_MAT4x3: var.type.name = "mat4x3"; break;
case eGL_FLOAT_MAT3x4: var.type.name = "mat3x4"; break;
case eGL_FLOAT_MAT3x2: var.type.name = "mat3x2"; break;
case eGL_FLOAT_MAT2x4: var.type.name = "mat2x4"; break;
case eGL_FLOAT_MAT2x3: var.type.name = "mat2x3"; break;
case eGL_DOUBLE_VEC4: var.type.name = "dvec4"; break;
case eGL_DOUBLE_VEC3: var.type.name = "dvec3"; break;
case eGL_DOUBLE_VEC2: var.type.name = "dvec2"; break;
case eGL_DOUBLE: var.type.name = "double"; break;
case eGL_DOUBLE_MAT4: var.type.name = "dmat4"; break;
case eGL_DOUBLE_MAT3: var.type.name = "dmat3"; break;
case eGL_DOUBLE_MAT2: var.type.name = "dmat2"; break;
case eGL_DOUBLE_MAT4x2: var.type.name = "dmat4x2"; break;
case eGL_DOUBLE_MAT4x3: var.type.name = "dmat4x3"; break;
case eGL_DOUBLE_MAT3x4: var.type.name = "dmat3x4"; break;
case eGL_DOUBLE_MAT3x2: var.type.name = "dmat3x2"; break;
case eGL_DOUBLE_MAT2x4: var.type.name = "dmat2x4"; break;
case eGL_DOUBLE_MAT2x3: var.type.name = "dmat2x3"; break;
case eGL_UNSIGNED_INT_VEC4: var.type.name = "uvec4"; break;
case eGL_UNSIGNED_INT_VEC3: var.type.name = "uvec3"; break;
case eGL_UNSIGNED_INT_VEC2: var.type.name = "uvec2"; break;
case eGL_UNSIGNED_INT: var.type.name = "uint"; break;
case eGL_BOOL_VEC4: var.type.name = "bvec4"; break;
case eGL_BOOL_VEC3: var.type.name = "bvec3"; break;
case eGL_BOOL_VEC2: var.type.name = "bvec2"; break;
case eGL_BOOL: var.type.name = "bool"; break;
case eGL_INT_VEC4: var.type.name = "ivec4"; break;
case eGL_INT_VEC3: var.type.name = "ivec3"; break;
case eGL_INT_VEC2: var.type.name = "ivec2"; break;
case eGL_INT: var.type.name = "int"; break;
default: break;
}
if(values[5] == -1 && values[2] >= 0)
{
var.byteOffset = values[2];
}
else if(values[5] >= 0)
{
var.byteOffset = values[5];
}
else
{
var.byteOffset = ~0U;
}
if(values[6] > 0)
var.type.flags |= ShaderVariableFlags::RowMajorMatrix;
var.type.matrixByteStride = (uint8_t)values[8];
var.type.arrayByteStride = (uint32_t)values[7];
bool bareUniform = false;
// for plain uniforms we won't get an array/matrix byte stride. Calculate tightly packed strides
if(values[3] == -1)
{
bareUniform = true;
// plain matrices are always column major, so this is the size of a column
var.type.flags &= ~ShaderVariableFlags::RowMajorMatrix;
const uint32_t elemByteStride = (var.type.baseType == VarType::Double) ? 8 : 4;
var.type.matrixByteStride = uint8_t(var.type.rows * elemByteStride);
// arrays are fetched as individual glGetUniform calls
var.type.arrayByteStride = 0;
}
// set vectors/scalars as row major for convenience, since that's how they're stored in the fv
// array.
switch(values[0])
{
case eGL_FLOAT_VEC4:
case eGL_FLOAT_VEC3:
case eGL_FLOAT_VEC2:
case eGL_FLOAT:
case eGL_DOUBLE_VEC4:
case eGL_DOUBLE_VEC3:
case eGL_DOUBLE_VEC2:
case eGL_DOUBLE:
case eGL_UNSIGNED_INT_VEC4:
case eGL_UNSIGNED_INT_VEC3:
case eGL_UNSIGNED_INT_VEC2:
case eGL_UNSIGNED_INT:
case eGL_BOOL_VEC4:
case eGL_BOOL_VEC3:
case eGL_BOOL_VEC2:
case eGL_BOOL:
case eGL_INT_VEC4:
case eGL_INT_VEC3:
case eGL_INT_VEC2:
case eGL_INT: var.type.flags |= ShaderVariableFlags::RowMajorMatrix; break;
default: break;
}
var.name.resize(values[1] - 1);
GL.glGetProgramResourceName(sepProg, query, varIdx, values[1], NULL, &var.name[0]);
rdcstr fullname = var.name;
int32_t c = values[1] - 1;
// trim off trailing [0] if it's an array
if(var.name[c - 3] == '[' && var.name[c - 2] == '0' && var.name[c - 1] == ']')
var.name.resize(c - 3);
else
var.type.elements = 1;
GLint topLevelStride = 0;
if(query == eGL_BUFFER_VARIABLE)
{
GLenum propName = eGL_TOP_LEVEL_ARRAY_STRIDE;
GL.glGetProgramResourceiv(sepProg, query, varIdx, 1, &propName, 1, NULL, &topLevelStride);
// if ARRAY_SIZE is 0 this is an unbounded array
if(values[4] == 0)
var.type.elements = ~0U;
}
rdcarray<ShaderConstant> *parentmembers = defaultBlock;
if(!bareUniform && values[3] < numParentBlocks)
{
parentmembers = &parentBlocks[values[3]];
}
if(parentmembers == NULL)
{
RDCWARN("Found variable '%s' without parent block index '%d'", var.name.c_str(), values[3]);
return;
}
char *nm = &var.name[0];
bool multiDimArray = false;
int arrayIdx = 0;
bool blockLevel = true;
int level = 0;
// reverse figure out structures and structure arrays
while(strchr(nm, '.') || strchr(nm, '['))
{
char *base = nm;
while(*nm != '.' && *nm != '[')
nm++;
// determine if we have an array index, and NULL out
// what's after the base variable name
bool isarray = (*nm == '[');
*nm = 0;
nm++;
arrayIdx = 0;
// if it's an array, get the index used
if(isarray)
{
// get array index, it's always a decimal number
while(*nm >= '0' && *nm <= '9')
{
arrayIdx *= 10;
arrayIdx += int(*nm) - int('0');
nm++;
}
RDCASSERT(*nm == ']');
*nm = 0;
nm++;
// skip forward to the child name
if(*nm == '.')
{
*nm = 0;
nm++;
}
else
{
// if there's no . after the array index, this is a multi-dimensional array.
multiDimArray = true;
}
}
// construct a parent variable
ShaderConstant parentVar;
parentVar.name = base;
parentVar.byteOffset = var.byteOffset;
parentVar.type.name = "struct";
parentVar.type.rows = 0;
parentVar.type.columns = 0;
parentVar.type.baseType = VarType::Struct;
parentVar.type.elements = isarray && !multiDimArray ? RDCMAX(1U, uint32_t(arrayIdx + 1)) : 1;
parentVar.type.matrixByteStride = 0;
parentVar.type.arrayByteStride = (uint32_t)topLevelStride;
// consider all block-level SSBO structs to have infinite elements if they are an array at all
// for structs that aren't the last struct in a block which can't be infinite, this will be
// fixup'd later by looking at the offset of subsequent elements
if(blockLevel && topLevelStride && isarray)
parentVar.type.elements = ~0U;
if(!blockLevel)
topLevelStride = 0;
// this is no longer block level after the first array, or the first struct member.
//
// this logic is because whether or not a block has a name affects what comes back. E.g.
// buffer ssbo { float ssbo_foo } ; will just be "ssbo_foo", whereas
// buffer ssbo { float ssbo_foo } root; will be "root.ssbo_foo"
//
// we only use blocklevel for the check above, to see if we are on a block-level array to mark
// it as unknown size (to be fixed later), so this check can be a little fuzzy as long as it
// doesn't have false positives.
if(isarray || level >= 1)
blockLevel = false;
bool found = false;
// if we can find the base variable already, we recurse into its members
for(size_t i = 0; i < parentmembers->size(); i++)
{
if((*parentmembers)[i].name == base)
{
// if we find the variable, update the # elements to account for this new array index
// and pick the minimum offset of all of our children as the parent offset. This is mostly
// just for sorting
(*parentmembers)[i].type.elements =
RDCMAX((*parentmembers)[i].type.elements, parentVar.type.elements);
(*parentmembers)[i].byteOffset = RDCMIN((*parentmembers)[i].byteOffset, parentVar.byteOffset);
parentmembers = &((*parentmembers)[i].type.members);
found = true;
break;
}
}
// if we didn't find the base variable, add it and recuse inside
if(!found)
{
parentmembers->push_back(parentVar);
parentmembers = &(parentmembers->back().type.members);
}
if(multiDimArray)
{
// if this is a multi-dimensional array, we've now selected the root array.
// We now iterate all the way down to the last element, then break out of the list so it can
// be added as an array.
//
// Note: this means that for float foo[4][3] we won't iterate here - we've already selected
// foo, and outside of the loop we'll push back a float[3] for each of foo[0], foo[1], foo[2],
// foo[3] that we encounter in this iteration process.
//
// For bar[4][3][2] we've selected bar, we'll then push back a [4] member and then outside of
// the loop we'll push back each of bar[.][0], bar[.][1], etc as a float[2]
while(*nm)
{
parentVar.name = StringFormat::Fmt("[%d]", arrayIdx);
found = false;
for(size_t i = 0; i < parentmembers->size(); i++)
{
if((*parentmembers)[i].name == parentVar.name)
{
parentmembers = &((*parentmembers)[i].type.members);
found = true;
break;
}
}
if(!found)
{
parentmembers->push_back(parentVar);
parentmembers = &(parentmembers->back().type.members);
}
arrayIdx = 0;
RDCASSERT(*nm == '[');
nm++;
while(*nm >= '0' && *nm <= '9')
{
arrayIdx *= 10;
arrayIdx += int(*nm) - int('0');
nm++;
}
RDCASSERT(*nm == ']');
*nm = 0;
nm++;
}
break;
}
// the 0th element of each array fills out the actual members, when we
// encounter an index above that we only use it to increase the type.elements
// member (which we've done by this point) and can stop recursing
//
// The exception is when we're looking at bare uniforms - there the struct members all have
// individual locations and are generally aggressively stripped by the driver.
// So then it's possible to get foo[0].bar.a and foo[1].bar.b - so higher indices can reveal
// more of the structure.
if(arrayIdx > 0 && !bareUniform)
{
parentmembers = NULL;
break;
}
level++;
}
if(parentmembers)
{
// if this is a bare uniform we need to be careful - just above we continued iterating for
// higher indices, because you can have cases that return e.g. foo[0].bar.a and foo[1].bar.b and
// get 'more information' from later indices, the full structure is not revealed under foo[0].
// However as a result of that it means we could see foo[0].bar.a and foo[1].bar.a - we need to
// be careful not to add the final 'a' twice. Check for duplicates and be sure it's really a
// duplicate.
bool duplicate = false;
// nm points into var.name's storage, so copy out to a temporary
rdcstr n = nm;
var.name = n;
if(bareUniform && !multiDimArray)
{
for(size_t i = 0; i < parentmembers->size(); i++)
{
if((*parentmembers)[i].name == var.name)
{
ShaderConstantType &oldtype = (*parentmembers)[i].type;
ShaderConstantType &newtype = var.type;
if(oldtype.rows != newtype.rows || oldtype.columns != newtype.columns ||
oldtype.baseType != newtype.baseType || oldtype.elements != newtype.elements)
{
RDCERR("When reconstructing %s, found duplicate but different final member %s",
fullname.c_str(), (*parentmembers)[i].name.c_str());
}
duplicate = true;
break;
}
}
}
if(duplicate)
return;
// for multidimensional arrays there will be no proper name, so name the variable by the index
if(multiDimArray)
var.name = StringFormat::Fmt("[%d]", arrayIdx);
parentmembers->push_back(var);
}
}