void ReconstructVarTree()

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);
  }
}