void CopyProgramAttribBindings(const GLHookSet &gl, GLuint progsrc, GLuint progdst,
                               ShaderReflection *refl)
{
  // copy over attrib bindings
  for(const SigParameter &sig : refl->inputSignature)
  {
    // skip built-ins
    if(sig.systemValue != ShaderBuiltin::Undefined)
      continue;

    GLint idx = gl.glGetAttribLocation(progsrc, sig.varName.c_str());
    if(idx >= 0)
      gl.glBindAttribLocation(progdst, (GLuint)idx, sig.varName.c_str());
  }
}
void SerialiseProgramBindings(SerialiserType &ser, CaptureState state, const GLHookSet &gl,
                              GLuint prog)
{
  std::vector<ProgramBinding> InputBindings;
  std::vector<ProgramBinding> OutputBindings;

  if(ser.IsWriting())
  {
    char buf[128] = {};

    for(int sigType = 0; sigType < 2; sigType++)
    {
      GLenum sigEnum = (sigType == 0 ? eGL_PROGRAM_INPUT : eGL_PROGRAM_OUTPUT);
      std::vector<ProgramBinding> &bindings = (sigType == 0 ? InputBindings : OutputBindings);

      int32_t NumAttributes = 0;
      gl.glGetProgramInterfaceiv(prog, sigEnum, eGL_ACTIVE_RESOURCES, (GLint *)&NumAttributes);
      bindings.reserve(NumAttributes);

      for(GLint i = 0; i < NumAttributes; i++)
      {
        gl.glGetProgramResourceName(prog, sigEnum, i, 128, NULL, buf);

        ProgramBinding bind;
        bind.Name = buf;

        if(sigType == 0)
          bind.Binding = gl.glGetAttribLocation(prog, buf);
        else
          bind.Binding = gl.glGetFragDataLocation(prog, buf);

        bindings.push_back(bind);
      }
    }
  }

  SERIALISE_ELEMENT(InputBindings);
  SERIALISE_ELEMENT(OutputBindings);

  if(ser.IsReading() && IsReplayMode(state))
  {
    for(int sigType = 0; sigType < 2; sigType++)
    {
      const std::vector<ProgramBinding> &bindings = (sigType == 0 ? InputBindings : OutputBindings);

      uint64_t used = 0;

      for(const ProgramBinding &bind : bindings)
      {
        if(bind.Binding >= 0)
        {
          uint64_t mask = 1ULL << bind.Binding;

          if(used & mask)
          {
            RDCWARN("Multiple %s items bound to location %d, ignoring %s",
                    sigType == 0 ? "attrib" : "fragdata", bind.Binding, bind.Name.c_str());
            continue;
          }

          used |= mask;

          if(!strncmp("gl_", bind.Name.c_str(), 3))
            continue;    // GL_INVALID_OPERATION if name starts with reserved gl_ prefix (for both
                         // glBindAttribLocation and glBindFragDataLocation)

          if(sigType == 0)
          {
            gl.glBindAttribLocation(prog, (GLuint)bind.Binding, bind.Name.c_str());
          }
          else
          {
            if(gl.glBindFragDataLocation)
            {
              gl.glBindFragDataLocation(prog, (GLuint)bind.Binding, bind.Name.c_str());
            }
            else
            {
              // glBindFragDataLocation is not core GLES, but it is in GL_EXT_blend_func_extended
              // TODO what to do if that extension is not supported
              RDCERR("glBindFragDataLocation is not supported!");
            }
          }
        }
      }
    }
  }
}