void MakeShaderReflection()

in renderdoc/driver/gl/gl_shader_refl.cpp [1227:2403]


void MakeShaderReflection(GLenum shadType, GLuint sepProg, ShaderReflection &refl,
                          const FixedFunctionVertexOutputs &outputUsage)
{
  refl.stage = MakeShaderStage(shadType);
  refl.debugInfo.entrySourceName = refl.entryPoint = "main";
  refl.encoding = ShaderEncoding::GLSL;
  refl.debugInfo.compiler = KnownShaderTool::Unknown;
  refl.debugInfo.encoding = ShaderEncoding::GLSL;

  if(shadType == eGL_COMPUTE_SHADER)
  {
    GL.glGetProgramiv(sepProg, eGL_COMPUTE_WORK_GROUP_SIZE,
                      (GLint *)refl.dispatchThreadsDimension.data());
  }
  else
  {
    RDCEraseEl(refl.dispatchThreadsDimension);
  }

  rdcarray<ShaderResource> &roresources = refl.readOnlyResources;
  rdcarray<ShaderResource> &rwresources = refl.readWriteResources;

  GLint numUniforms = 0;
  GL.glGetProgramInterfaceiv(sepProg, eGL_UNIFORM, eGL_ACTIVE_RESOURCES, &numUniforms);

  const size_t numProps = 7;

  GLenum resProps[numProps] = {
      eGL_TYPE,       eGL_NAME_LENGTH, eGL_LOCATION,     eGL_BLOCK_INDEX,
      eGL_ARRAY_SIZE, eGL_OFFSET,      eGL_IS_ROW_MAJOR,
  };

  for(GLint u = 0; u < numUniforms; u++)
  {
    GLint values[numProps];
    GL.glGetProgramResourceiv(sepProg, eGL_UNIFORM, u, numProps, resProps, numProps, NULL, values);

    ShaderResource res;
    res.isReadOnly = true;
    res.isTexture = true;
    res.variableType.rows = 1;
    res.variableType.columns = 4;
    res.variableType.elements = 1;
    res.variableType.arrayByteStride = 0;
    res.variableType.matrixByteStride = 0;

    res.descriptorType = DescriptorType::ImageSampler;

    // float samplers
    if(values[0] == eGL_SAMPLER_BUFFER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "samplerBuffer";
      res.variableType.baseType = VarType::Float;
      res.descriptorType = DescriptorType::TypedBuffer;
    }
    else if(values[0] == eGL_SAMPLER_1D)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "sampler1D";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_1D_ARRAY)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "sampler1DArray";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_1D_SHADOW)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "sampler1DShadow";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_1D_ARRAY_SHADOW)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "sampler1DArrayShadow";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "sampler2D";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_ARRAY)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "sampler2DArray";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_SHADOW)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "sampler2DShadow";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_ARRAY_SHADOW)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "sampler2DArrayShadow";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_RECT)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "sampler2DRect";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_RECT_SHADOW)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "sampler2DRectShadow";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_3D)
    {
      res.textureType = TextureType::Texture3D;
      res.variableType.name = "sampler3D";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_CUBE)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "samplerCube";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_CUBE_SHADOW)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "samplerCubeShadow";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_CUBE_MAP_ARRAY)
    {
      res.textureType = TextureType::TextureCubeArray;
      res.variableType.name = "samplerCubeArray";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_MULTISAMPLE)
    {
      res.textureType = TextureType::Texture2DMS;
      res.variableType.name = "sampler2DMS";
      res.variableType.baseType = VarType::Float;
    }
    else if(values[0] == eGL_SAMPLER_2D_MULTISAMPLE_ARRAY)
    {
      res.textureType = TextureType::Texture2DMSArray;
      res.variableType.name = "sampler2DMSArray";
      res.variableType.baseType = VarType::Float;
    }
    // int samplers
    else if(values[0] == eGL_INT_SAMPLER_BUFFER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "isamplerBuffer";
      res.variableType.baseType = VarType::SInt;
      res.descriptorType = DescriptorType::TypedBuffer;
    }
    else if(values[0] == eGL_INT_SAMPLER_1D)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "isampler1D";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_1D_ARRAY)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "isampler1DArray";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_2D)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "isampler2D";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_2D_ARRAY)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "isampler2DArray";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_2D_RECT)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "isampler2DRect";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_3D)
    {
      res.textureType = TextureType::Texture3D;
      res.variableType.name = "isampler3D";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_CUBE)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "isamplerCube";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_CUBE_MAP_ARRAY)
    {
      res.textureType = TextureType::TextureCubeArray;
      res.variableType.name = "isamplerCubeArray";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_2D_MULTISAMPLE)
    {
      res.textureType = TextureType::Texture2DMS;
      res.variableType.name = "isampler2DMS";
      res.variableType.baseType = VarType::SInt;
    }
    else if(values[0] == eGL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY)
    {
      res.textureType = TextureType::Texture2DMSArray;
      res.variableType.name = "isampler2DMSArray";
      res.variableType.baseType = VarType::SInt;
    }
    // unsigned int samplers
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_BUFFER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "usamplerBuffer";
      res.variableType.baseType = VarType::UInt;
      res.descriptorType = DescriptorType::TypedBuffer;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_1D)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "usampler1D";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_1D_ARRAY)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "usampler1DArray";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_2D)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "usampler2D";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_2D_ARRAY)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "usampler2DArray";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_2D_RECT)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "usampler2DRect";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_3D)
    {
      res.textureType = TextureType::Texture3D;
      res.variableType.name = "usampler3D";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_CUBE)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "usamplerCube";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY)
    {
      res.textureType = TextureType::TextureCubeArray;
      res.variableType.name = "usamplerCubeArray";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE)
    {
      res.textureType = TextureType::Texture2DMS;
      res.variableType.name = "usampler2DMS";
      res.variableType.baseType = VarType::UInt;
    }
    else if(values[0] == eGL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY)
    {
      res.textureType = TextureType::Texture2DMSArray;
      res.variableType.name = "usampler2DMSArray";
      res.variableType.baseType = VarType::UInt;
    }
    // float images
    else if(values[0] == eGL_IMAGE_BUFFER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "imageBuffer";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
      res.descriptorType = DescriptorType::ReadWriteTypedBuffer;
    }
    else if(values[0] == eGL_IMAGE_1D)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "image1D";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_1D_ARRAY)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "image1DArray";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_2D)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "image2D";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_2D_ARRAY)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "image2DArray";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_2D_RECT)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "image2DRect";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_3D)
    {
      res.textureType = TextureType::Texture3D;
      res.variableType.name = "image3D";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_CUBE)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "imageCube";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_CUBE_MAP_ARRAY)
    {
      res.textureType = TextureType::TextureCubeArray;
      res.variableType.name = "imageCubeArray";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_2D_MULTISAMPLE)
    {
      res.textureType = TextureType::Texture2DMS;
      res.variableType.name = "image2DMS";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_IMAGE_2D_MULTISAMPLE_ARRAY)
    {
      res.textureType = TextureType::Texture2DMSArray;
      res.variableType.name = "image2DMSArray";
      res.variableType.baseType = VarType::Float;
      res.isReadOnly = false;
    }
    // int images
    else if(values[0] == eGL_INT_IMAGE_BUFFER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "iimageBuffer";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
      res.descriptorType = DescriptorType::ReadWriteTypedBuffer;
    }
    else if(values[0] == eGL_INT_IMAGE_1D)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "iimage1D";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_1D_ARRAY)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "iimage1DArray";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_2D)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "iimage2D";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_2D_ARRAY)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "iimage2DArray";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_2D_RECT)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "iimage2DRect";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_3D)
    {
      res.textureType = TextureType::Texture3D;
      res.variableType.name = "iimage3D";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_CUBE)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "iimageCube";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_CUBE_MAP_ARRAY)
    {
      res.textureType = TextureType::TextureCubeArray;
      res.variableType.name = "iimageCubeArray";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_2D_MULTISAMPLE)
    {
      res.textureType = TextureType::Texture2DMS;
      res.variableType.name = "iimage2DMS";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_INT_IMAGE_2D_MULTISAMPLE_ARRAY)
    {
      res.textureType = TextureType::Texture2DMSArray;
      res.variableType.name = "iimage2DMSArray";
      res.variableType.baseType = VarType::SInt;
      res.isReadOnly = false;
    }
    // unsigned int images
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_BUFFER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "uimageBuffer";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
      res.descriptorType = DescriptorType::ReadWriteTypedBuffer;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_1D)
    {
      res.textureType = TextureType::Texture1D;
      res.variableType.name = "uimage1D";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_1D_ARRAY)
    {
      res.textureType = TextureType::Texture1DArray;
      res.variableType.name = "uimage1DArray";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_2D)
    {
      res.textureType = TextureType::Texture2D;
      res.variableType.name = "uimage2D";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_2D_ARRAY)
    {
      res.textureType = TextureType::Texture2DArray;
      res.variableType.name = "uimage2DArray";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_2D_RECT)
    {
      res.textureType = TextureType::TextureRect;
      res.variableType.name = "uimage2DRect";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_3D)
    {
      res.textureType = TextureType::Texture3D;
      res.variableType.name = "uimage3D";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_CUBE)
    {
      res.textureType = TextureType::TextureCube;
      res.variableType.name = "uimageCube";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY)
    {
      res.textureType = TextureType::TextureCubeArray;
      res.variableType.name = "uimageCubeArray";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE)
    {
      res.textureType = TextureType::Texture2DMS;
      res.variableType.name = "uimage2DMS";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    else if(values[0] == eGL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY)
    {
      res.textureType = TextureType::Texture2DMSArray;
      res.variableType.name = "uimage2DMSArray";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
    }
    // atomic counter
    else if(values[0] == eGL_UNSIGNED_INT_ATOMIC_COUNTER)
    {
      res.textureType = TextureType::Buffer;
      res.variableType.name = "atomic_uint";
      res.variableType.baseType = VarType::UInt;
      res.isReadOnly = false;
      res.isTexture = false;
      res.variableType.columns = 1;
      res.descriptorType = DescriptorType::ReadWriteBuffer;
    }
    else
    {
      // not a sampler
      continue;
    }

    if(!res.isReadOnly && res.textureType == TextureType::Buffer)
      res.descriptorType = DescriptorType::ReadWriteImage;

    res.hasSampler = res.isReadOnly;

    char *namebuf = new char[values[1] + 1];
    GL.glGetProgramResourceName(sepProg, eGL_UNIFORM, u, values[1], NULL, namebuf);
    namebuf[values[1]] = 0;

    rdcstr name = namebuf;

    delete[] namebuf;

    res.name = name;

    rdcarray<ShaderResource> &reslist = (res.isReadOnly ? roresources : rwresources);

    reslist.push_back(res);

    // array of samplers
    if(values[4] > 1)
    {
      name = name.substr(0, name.length() - 3);    // trim off [0] on the end
      for(int i = 1; i < values[4]; i++)
      {
        rdcstr arrname = StringFormat::Fmt("%s[%d]", name.c_str(), i);

        res.name = arrname;

        reslist.push_back(res);
      }
    }
  }

  rdcarray<size_t> ssbos;
  uint32_t ssboMembers = 0;

  GLint numSSBOs = 0;
  if(HasExt[ARB_shader_storage_buffer_object])
  {
    GL.glGetProgramInterfaceiv(sepProg, eGL_SHADER_STORAGE_BLOCK, eGL_ACTIVE_RESOURCES, &numSSBOs);

    for(GLint u = 0; u < numSSBOs; u++)
    {
      GLenum propName = eGL_NAME_LENGTH;
      GLint len;
      GL.glGetProgramResourceiv(sepProg, eGL_SHADER_STORAGE_BLOCK, u, 1, &propName, 1, NULL, &len);

      char *nm = new char[len + 1];
      GL.glGetProgramResourceName(sepProg, eGL_SHADER_STORAGE_BLOCK, u, len + 1, NULL, nm);

      ShaderResource res;
      res.isReadOnly = false;
      res.isTexture = false;
      res.textureType = TextureType::Buffer;
      res.variableType.rows = 0;
      res.variableType.columns = 0;
      res.variableType.elements = 1;
      res.variableType.arrayByteStride = 0;
      res.variableType.matrixByteStride = 0;
      res.variableType.name = "buffer";
      res.variableType.baseType = VarType::UInt;
      res.name = nm;
      res.descriptorType = DescriptorType::ReadWriteBuffer;

      GLint numMembers = 0;

      propName = eGL_NUM_ACTIVE_VARIABLES;
      GL.glGetProgramResourceiv(sepProg, eGL_SHADER_STORAGE_BLOCK, u, 1, &propName, 1, NULL,
                                (GLint *)&numMembers);

      char *arr = strchr(nm, '[');
      const bool isArray = (arr != NULL);

      uint32_t arrayIdx = 0;
      if(isArray)
      {
        arr++;
        while(*arr >= '0' && *arr <= '9')
        {
          arrayIdx *= 10;
          arrayIdx += int(*arr) - int('0');
          arr++;
        }
      }

      ssbos.push_back(rwresources.size());
      rwresources.push_back(res);

      // only count members from the first array index
      if(!isArray || arrayIdx == 0)
        ssboMembers += numMembers;

      delete[] nm;
    }
  }

  {
    rdcarray<ShaderConstant> *members = new rdcarray<ShaderConstant>[ssbos.size()];

    for(uint32_t i = 0; i < ssboMembers; i++)
    {
      ReconstructVarTree(eGL_BUFFER_VARIABLE, sepProg, i, (GLint)ssbos.size(), members, NULL);
    }

    // if we have members in an array ssbo, broadcast this to any ssbo with the same basename
    // without members, since the variables will only be reported as belonging to one block
    for(size_t ssbo = 0; ssbo < ssbos.size(); ssbo++)
    {
      rdcstr basename = rwresources[ssbos[ssbo]].name;
      int arrOffs = basename.indexOf('[');
      if(arrOffs > 0)
      {
        basename.erase(arrOffs, basename.size());

        if(!members[ssbo].empty())
        {
          for(size_t ssbo2 = 0; ssbo2 < ssbos.size(); ssbo2++)
          {
            if(!members[ssbo2].empty())
              continue;

            rdcstr basename2 = rwresources[ssbos[ssbo2]].name;
            arrOffs = basename2.indexOf('[');
            if(arrOffs > 0)
            {
              basename2.erase(arrOffs, basename2.size());

              if(basename == basename2)
              {
                members[ssbo2] = members[ssbo];
              }
            }
          }
        }
      }
    }

    for(size_t ssbo = 0; ssbo < ssbos.size(); ssbo++)
    {
      sort(members[ssbo]);

      rdcstr basename = rwresources[ssbos[ssbo]].name;
      int arrOffs = basename.indexOf('[');
      if(arrOffs > 0)
        basename.erase(arrOffs, basename.size());

      if(!members[ssbo].empty() && basename == members[ssbo][0].name)
        std::swap(rwresources[ssbos[ssbo]].variableType.members, members[ssbo][0].type.members);
      else
        std::swap(rwresources[ssbos[ssbo]].variableType.members, members[ssbo]);
    }

    // patch-up reflection data. For top-level arrays use the stride & rough size to calculate the
    // number of elements, and make all child byteOffset values relative to their parent
    for(size_t ssbo = 0; ssbo < ssbos.size(); ssbo++)
    {
      rdcarray<ShaderConstant> &ssboVars = rwresources[ssbos[ssbo]].variableType.members;

      // can't make perfect guesses of struct alignment but assume std430 for ssbos
      for(ShaderConstant &member : ssboVars)
        FixupStructOffsetsAndSize(false, member);

      for(size_t rootMember = 0; rootMember + 1 < ssboVars.size(); rootMember++)
      {
        ShaderConstant &member = ssboVars[rootMember];

        const uint32_t memberSizeBound = ssboVars[rootMember + 1].byteOffset - member.byteOffset;
        const uint32_t stride = member.type.arrayByteStride;

        if(stride != 0 && member.type.elements == ~0U)
        {
          if(memberSizeBound >= 2 * stride)
            member.type.elements = memberSizeBound / stride;
          else
            member.type.elements = 1;
        }
      }
    }

    delete[] members;
  }

  rdcarray<ShaderConstant> globalUniforms;

  GLint numUBOs = 0;
  rdcarray<rdcstr> uboNames;
  rdcarray<ShaderConstant> *ubos = NULL;

  {
    GL.glGetProgramInterfaceiv(sepProg, eGL_UNIFORM_BLOCK, eGL_ACTIVE_RESOURCES, &numUBOs);

    ubos = new rdcarray<ShaderConstant>[numUBOs];
    uboNames.resize(numUBOs);

    for(GLint u = 0; u < numUBOs; u++)
    {
      GLenum nameLen = eGL_NAME_LENGTH;
      GLint len;
      GL.glGetProgramResourceiv(sepProg, eGL_UNIFORM_BLOCK, u, 1, &nameLen, 1, NULL, &len);

      char *nm = new char[len + 1];
      GL.glGetProgramResourceName(sepProg, eGL_UNIFORM_BLOCK, u, len + 1, NULL, nm);
      uboNames[u] = nm;
      delete[] nm;
    }
  }

  for(GLint u = 0; u < numUniforms; u++)
  {
    ReconstructVarTree(eGL_UNIFORM, sepProg, u, numUBOs, ubos, &globalUniforms);
  }

  refl.constantBlocks.reserve(numUBOs + (globalUniforms.empty() ? 0 : 1));

  if(ubos)
  {
    for(int i = 0; i < numUBOs; i++)
    {
      if(!ubos[i].empty())
      {
        ConstantBlock cblock;
        cblock.name = uboNames[i];
        cblock.bufferBacked = true;

        GLenum bufSize = eGL_BUFFER_DATA_SIZE;
        GL.glGetProgramResourceiv(sepProg, eGL_UNIFORM_BLOCK, i, 1, &bufSize, 1, NULL,
                                  (GLint *)&cblock.byteSize);

        sort(ubos[i]);

        // can't make perfect guesses of struct alignment but assume std140 for ubos
        for(ShaderConstant &member : ubos[i])
          FixupStructOffsetsAndSize(true, member);

        std::swap(cblock.variables, ubos[i]);

        refl.constantBlocks.push_back(cblock);
      }
    }
  }

  if(!globalUniforms.empty())
  {
    ConstantBlock globals;
    globals.name = "$Globals";
    globals.bufferBacked = false;

    // global uniforms have no defined order, location will be per implementation, so sort instead
    // alphabetically
    namesort(globalUniforms);
    std::swap(globals.variables, globalUniforms);

    refl.constantBlocks.push_back(globals);
  }

  delete[] ubos;

  for(int sigType = 0; sigType < 2; sigType++)
  {
    GLenum sigEnum = (sigType == 0 ? eGL_PROGRAM_INPUT : eGL_PROGRAM_OUTPUT);
    rdcarray<SigParameter> *sigArray = (sigType == 0 ? &refl.inputSignature : &refl.outputSignature);

    GLint numInputs;
    GL.glGetProgramInterfaceiv(sepProg, sigEnum, eGL_ACTIVE_RESOURCES, &numInputs);

    if(numInputs > 0)
    {
      rdcarray<SigParameter> sigs;
      sigs.reserve(numInputs);

      uint32_t regIndex = 0;

      for(GLint i = 0; i < numInputs; i++)
      {
        GLenum props[] = {eGL_NAME_LENGTH, eGL_TYPE, eGL_LOCATION, eGL_ARRAY_SIZE,
                          eGL_LOCATION_COMPONENT};
        GLint values[] = {0, 0, 0, 0, 0};

        GLsizei numSigProps = (GLsizei)ARRAY_COUNT(props);

        // GL_LOCATION_COMPONENT not supported on core <4.4 (or without GL_ARB_enhanced_layouts)
        // on GLES, or when we don't have native program interface query
        if(!HasExt[ARB_enhanced_layouts] || !HasExt[ARB_program_interface_query])
          numSigProps--;
        GL.glGetProgramResourceiv(sepProg, sigEnum, i, numSigProps, props, numSigProps, NULL, values);

        char *nm = new char[values[0] + 1];
        GL.glGetProgramResourceName(sepProg, sigEnum, i, values[0] + 1, NULL, nm);

        SigParameter sig;

        sig.varName = nm;
        sig.semanticIndex = 0;
        sig.needSemanticIndex = false;
        sig.stream = 0;

        int rows = 1;

        switch(values[1])
        {
          case eGL_DOUBLE:
          case eGL_DOUBLE_VEC2:
          case eGL_DOUBLE_VEC3:
          case eGL_DOUBLE_VEC4:
          case eGL_DOUBLE_MAT4:
          case eGL_DOUBLE_MAT4x3:
          case eGL_DOUBLE_MAT4x2:
          case eGL_DOUBLE_MAT3:
          case eGL_DOUBLE_MAT3x4:
          case eGL_DOUBLE_MAT3x2:
          case eGL_DOUBLE_MAT2:
          case eGL_DOUBLE_MAT2x3:
          case eGL_DOUBLE_MAT2x4: sig.varType = VarType::Double; break;
          case eGL_FLOAT:
          case eGL_FLOAT_VEC2:
          case eGL_FLOAT_VEC3:
          case eGL_FLOAT_VEC4:
          case eGL_FLOAT_MAT4:
          case eGL_FLOAT_MAT4x3:
          case eGL_FLOAT_MAT4x2:
          case eGL_FLOAT_MAT3:
          case eGL_FLOAT_MAT3x4:
          case eGL_FLOAT_MAT3x2:
          case eGL_FLOAT_MAT2:
          case eGL_FLOAT_MAT2x3:
          case eGL_FLOAT_MAT2x4: sig.varType = VarType::Float; break;
          case eGL_INT:
          case eGL_INT_VEC2:
          case eGL_INT_VEC3:
          case eGL_INT_VEC4: sig.varType = VarType::SInt; break;
          case eGL_UNSIGNED_INT:
          case eGL_UNSIGNED_INT_VEC2:
          case eGL_UNSIGNED_INT_VEC3:
          case eGL_UNSIGNED_INT_VEC4: sig.varType = VarType::UInt; break;
          case eGL_BOOL:
          case eGL_BOOL_VEC2:
          case eGL_BOOL_VEC3:
          case eGL_BOOL_VEC4: sig.varType = VarType::Bool; break;
          default:
            sig.varType = VarType::Float;
            RDCWARN("Unhandled signature element type %s", ToStr((GLenum)values[1]).c_str());
        }

        switch(values[1])
        {
          case eGL_FLOAT:
          case eGL_DOUBLE:
          case eGL_INT:
          case eGL_UNSIGNED_INT:
          case eGL_BOOL:
            sig.compCount = 1;
            sig.regChannelMask = 0x1;
            break;
          case eGL_FLOAT_VEC2:
          case eGL_DOUBLE_VEC2:
          case eGL_INT_VEC2:
          case eGL_UNSIGNED_INT_VEC2:
          case eGL_BOOL_VEC2:
            sig.compCount = 2;
            sig.regChannelMask = 0x3;
            break;
          case eGL_FLOAT_VEC3:
          case eGL_DOUBLE_VEC3:
          case eGL_INT_VEC3:
          case eGL_UNSIGNED_INT_VEC3:
          case eGL_BOOL_VEC3:
            sig.compCount = 3;
            sig.regChannelMask = 0x7;
            break;
          case eGL_FLOAT_VEC4:
          case eGL_DOUBLE_VEC4:
          case eGL_INT_VEC4:
          case eGL_UNSIGNED_INT_VEC4:
          case eGL_BOOL_VEC4:
            sig.compCount = 4;
            sig.regChannelMask = 0xf;
            break;
          case eGL_FLOAT_MAT4:
          case eGL_DOUBLE_MAT4:
            sig.compCount = 4;
            rows = 4;
            sig.regChannelMask = 0xf;
            break;
          case eGL_FLOAT_MAT4x3:
          case eGL_DOUBLE_MAT4x3:
            sig.compCount = 4;
            rows = 3;
            sig.regChannelMask = 0xf;
            break;
          case eGL_FLOAT_MAT4x2:
          case eGL_DOUBLE_MAT4x2:
            sig.compCount = 4;
            rows = 2;
            sig.regChannelMask = 0xf;
            break;
          case eGL_FLOAT_MAT3:
          case eGL_DOUBLE_MAT3:
            sig.compCount = 3;
            rows = 3;
            sig.regChannelMask = 0x7;
            break;
          case eGL_FLOAT_MAT3x4:
          case eGL_DOUBLE_MAT3x4:
            sig.compCount = 3;
            rows = 4;
            sig.regChannelMask = 0x7;
            break;
          case eGL_FLOAT_MAT3x2:
          case eGL_DOUBLE_MAT3x2:
            sig.compCount = 3;
            rows = 2;
            sig.regChannelMask = 0x7;
            break;
          case eGL_FLOAT_MAT2:
          case eGL_DOUBLE_MAT2:
            sig.compCount = 2;
            rows = 2;
            sig.regChannelMask = 0x3;
            break;
          case eGL_FLOAT_MAT2x3:
          case eGL_DOUBLE_MAT2x3:
            sig.compCount = 2;
            rows = 3;
            sig.regChannelMask = 0x3;
            break;
          case eGL_FLOAT_MAT2x4:
          case eGL_DOUBLE_MAT2x4:
            sig.compCount = 2;
            rows = 4;
            sig.regChannelMask = 0x3;
            break;
          default:
            RDCWARN("Unhandled signature element type %s", ToStr((GLenum)values[1]).c_str());
            sig.compCount = 4;
            sig.regChannelMask = 0xf;
            break;
        }

        sig.systemValue = ShaderBuiltin::Undefined;

        const char *varname = nm;

        if(!strncmp(varname, "gl_PerVertex.", 13))
          varname += 13;

#define IS_BUILTIN(builtin) !strncmp(varname, builtin, sizeof(builtin) - 1)

        // some vertex outputs can be reflected (especially by glslang) if they're just declared and
        // not used, which is quite common with redeclaring outputs for separable programs - either
        // by the program or by us. So instead use our manual quick-and-dirty usage check to skip
        // potential false-positives.
        bool unused = false;
        for(FFVertexOutput ffoutput : ::values<FFVertexOutput>())
        {
          // we consider an output used if we encounter a '=' before either a ';' or the end of the
          // string
          rdcstr outName = ToStr(ffoutput);

          // we do a substring search so that gl_ClipDistance matches gl_ClipDistance[0]
          if(strstr(varname, outName.c_str()))
          {
            unused = !outputUsage.used[(int)ffoutput];
            break;
          }
        }

        if(unused)
        {
          delete[] nm;
          continue;
        }

        // VS built-in inputs
        if(IS_BUILTIN("gl_VertexID"))
          sig.systemValue = ShaderBuiltin::VertexIndex;
        if(IS_BUILTIN("gl_InstanceID"))
          sig.systemValue = ShaderBuiltin::InstanceIndex;
        if(IS_BUILTIN("gl_BaseVertex"))
          sig.systemValue = ShaderBuiltin::BaseVertex;
        if(IS_BUILTIN("gl_BaseInstance"))
          sig.systemValue = ShaderBuiltin::BaseInstance;
        if(IS_BUILTIN("gl_DrawID"))
          sig.systemValue = ShaderBuiltin::DrawIndex;

        // VS built-in outputs
        if(IS_BUILTIN("gl_Position"))
          sig.systemValue = ShaderBuiltin::Position;
        if(IS_BUILTIN("gl_PointSize"))
          sig.systemValue = ShaderBuiltin::PointSize;
        if(IS_BUILTIN("gl_ClipDistance"))
          sig.systemValue = ShaderBuiltin::ClipDistance;

        // TCS built-in inputs
        if(IS_BUILTIN("gl_PatchVerticesIn"))
          sig.systemValue = ShaderBuiltin::PatchNumVertices;
        if(IS_BUILTIN("gl_PrimitiveID"))
          sig.systemValue = ShaderBuiltin::PrimitiveIndex;
        if(IS_BUILTIN("gl_InvocationID"))
          sig.systemValue = ShaderBuiltin::OutputControlPointIndex;

        // TCS built-in outputs
        if(IS_BUILTIN("gl_TessLevelOuter"))
          sig.systemValue = ShaderBuiltin::OuterTessFactor;
        if(IS_BUILTIN("gl_TessLevelInner"))
          sig.systemValue = ShaderBuiltin::InsideTessFactor;

        // TES built-in inputs
        if(IS_BUILTIN("gl_TessCoord"))
          sig.systemValue = ShaderBuiltin::DomainLocation;
        if(IS_BUILTIN("gl_PatchVerticesIn"))
          sig.systemValue = ShaderBuiltin::PatchNumVertices;
        if(IS_BUILTIN("gl_PrimitiveID"))
          sig.systemValue = ShaderBuiltin::PrimitiveIndex;

        // GS built-in inputs
        if(IS_BUILTIN("gl_PrimitiveIDIn"))
          sig.systemValue = ShaderBuiltin::PrimitiveIndex;
        if(IS_BUILTIN("gl_InvocationID") && shadType == eGL_GEOMETRY_SHADER)
          sig.systemValue = ShaderBuiltin::GSInstanceIndex;
        if(IS_BUILTIN("gl_Layer"))
          sig.systemValue = ShaderBuiltin::RTIndex;
        if(IS_BUILTIN("gl_ViewID_OVR"))
          sig.systemValue = ShaderBuiltin::MultiViewIndex;
        if(IS_BUILTIN("gl_ViewportIndex"))
          sig.systemValue = ShaderBuiltin::ViewportIndex;

        // GS built-in outputs
        if(IS_BUILTIN("gl_Layer"))
          sig.systemValue = ShaderBuiltin::RTIndex;
        if(IS_BUILTIN("gl_ViewportIndex"))
          sig.systemValue = ShaderBuiltin::ViewportIndex;

        // PS built-in inputs
        if(IS_BUILTIN("gl_FragCoord"))
          sig.systemValue = ShaderBuiltin::Position;
        if(IS_BUILTIN("gl_FrontFacing"))
          sig.systemValue = ShaderBuiltin::IsFrontFace;
        if(IS_BUILTIN("gl_PointCoord"))
          sig.systemValue = ShaderBuiltin::RTIndex;
        if(IS_BUILTIN("gl_SampleID"))
          sig.systemValue = ShaderBuiltin::MSAASampleIndex;
        if(IS_BUILTIN("gl_SamplePosition"))
          sig.systemValue = ShaderBuiltin::MSAASamplePosition;
        if(IS_BUILTIN("gl_SampleMaskIn"))
          sig.systemValue = ShaderBuiltin::MSAACoverage;

        // PS built-in outputs
        if(IS_BUILTIN("gl_FragDepth"))
          sig.systemValue = ShaderBuiltin::DepthOutput;
        if(IS_BUILTIN("gl_SampleMask"))
          sig.systemValue = ShaderBuiltin::MSAACoverage;
        if(IS_BUILTIN("gl_FragStencilRefARB"))
          sig.systemValue = ShaderBuiltin::StencilReference;

        // CS built-in inputs
        if(IS_BUILTIN("gl_NumWorkGroups"))
          sig.systemValue = ShaderBuiltin::DispatchSize;
        if(IS_BUILTIN("gl_WorkGroupID"))
          sig.systemValue = ShaderBuiltin::GroupIndex;
        if(IS_BUILTIN("gl_LocalInvocationID"))
          sig.systemValue = ShaderBuiltin::GroupThreadIndex;
        if(IS_BUILTIN("gl_GlobalInvocationID"))
          sig.systemValue = ShaderBuiltin::DispatchThreadIndex;
        if(IS_BUILTIN("gl_LocalInvocationIndex"))
          sig.systemValue = ShaderBuiltin::GroupFlatIndex;

#undef IS_BUILTIN
        if(sig.systemValue == ShaderBuiltin::Undefined)
          sig.regIndex = values[2] >= 0 ? values[2] : ~0U;
        else
          sig.regIndex = 0;

        if(shadType == eGL_FRAGMENT_SHADER && sigEnum == eGL_PROGRAM_OUTPUT &&
           sig.systemValue == ShaderBuiltin::Undefined)
          sig.systemValue = ShaderBuiltin::ColorOutput;

        // don't apply location component for built-ins
        if(sig.systemValue == ShaderBuiltin::Undefined)
          sig.regChannelMask <<= values[4];

        sig.channelUsedMask = sig.regChannelMask;

        if(values[3] <= 1)
        {
          AddSigParameter(sigs, regIndex, sig, nm, rows, -1);
        }
        else
        {
          rdcstr basename = nm;
          if(basename[basename.size() - 3] == '[' && basename[basename.size() - 2] == '0' &&
             basename[basename.size() - 1] == ']')
          {
            basename.resize(basename.size() - 3);
            for(int a = 0; a < values[3]; a++)
              AddSigParameter(sigs, regIndex, sig, basename.c_str(), rows, a);
          }
          else
          {
            RDCWARN("Got signature parameter %s with array size %d but no [0] suffix", nm, values[3]);
            AddSigParameter(sigs, regIndex, sig, nm, rows, -1);
          }
        }

        delete[] nm;
      }
      struct sig_param_sort
      {
        bool operator()(const SigParameter &a, const SigParameter &b)
        {
          if(a.systemValue == b.systemValue)
          {
            if(a.regIndex != b.regIndex)
              return a.regIndex < b.regIndex;

            return a.varName < b.varName;
          }

          if(a.systemValue == ShaderBuiltin::Undefined)
            return false;
          if(b.systemValue == ShaderBuiltin::Undefined)
            return true;

          return a.systemValue < b.systemValue;
        }
      };

      std::sort(sigs.begin(), sigs.end(), sig_param_sort());

      *sigArray = sigs;
    }
  }

  // TODO: fill in Interfaces with shader subroutines?
}