Code Review: RViz to fix slow loading on remote master

ros

To Reproduce

ROS1에서는 노드간의 통신을 위해 master가 필요하다. 보통은 하나의 PC에서 사용하여 localhost로 설정을 해두지만, 같은 네트워크 간에서 연결할 때는 마스터로 사용하는 경로인 MASTER_URI가 지정된다.

같은 Wifi 라우터를 통해 통신하는 경우에 RViz의 데이터 수신이 다소 느리긴 하여도 열리는 시간이 오래걸리진 않았다.

이번 대회를 하면서 LTE망+ZeroTier를 사용하면서 지역에 따라 굉장히 느리게 rviz가 열리었다. 특이점은 Ctrl+C를 누르면 rviz 창이 열렸다가 추후 다시 종료되게 되는데, 아마 로딩되는 프로세스가 죽고 그 이후 단계인 rviz 창이 열렸다가 종료되는 것으로 생각되어 최적화를 통해 해결할 수 있지 않을까 생각하였다.

rviz에 설정된 내용이 많아 무거울 것으로 추정하여, rviz -d filename 대신 기본 rviz를 실행하면 상대적으로 빠르게 열리지만, 이후 config를 로드하면 완전히 멈춰버린다.

  • LTE ROS_IP + ZeroTier
  • RViz with custom config

Code Review

main.cpp

#include <QApplication>
 
#include <rviz/visualizer_app.h>
 
int main(int argc, char** argv)
{
  QApplication qapp(argc, argv);
 
  rviz::VisualizerApp vapp;
  if (vapp.init(argc, argv))
  {
    return qapp.exec();
  }
  else
  {
    return 1;
  }
}

visulizer_app.cpp

// L112 - 117
bool VisualizerApp::init(int argc, char** argv)
{
  ROS_INFO("rviz version %s", get_version().c_str());
  ROS_INFO("compiled against Qt version " QT_VERSION_STR);
  ROS_INFO("compiled against OGRE version %d.%d.%d%s (%s)", OGRE_VERSION_MAJOR, OGRE_VERSION_MINOR,
           OGRE_VERSION_PATCH, OGRE_VERSION_SUFFIX, OGRE_VERSION_NAME);

main.cpp에서 불러오는 visualizer_app에 처음 rviz를 켤때 bash에 나오는 텍스트와 같다.

# bash에 나타나는 텍스트
# ubuntu@ubuntu:~$ rviz
[ INFO] [1693791545.852476454]: rviz version 1.14.20
[ INFO] [1693791545.852556164]: compiled against Qt version 5.12.8
[ INFO] [1693791545.852581906]: compiled against OGRE version 1.9.0 (Ghadamon)
[ INFO] [1693791545.862524804]: Forcing OpenGl version 0.
[ INFO] [1693791546.227579859]: Stereo is NOT SUPPORTED
[ INFO] [1693791546.227666094]: OpenGL device: Mesa Intel(R) UHD Graphics 620 (KBL GT2)
[ INFO] [1693791546.227701501]: OpenGl version: 4.6 (GLSL 4.6) limited to GLSL 1.4 on Mesa system.

그리고 master를 기다리는 부분은 아래와 같다.

/// ...
 
#if CATCH_EXCEPTIONS
  try
  {
#endif
    ros::init(argc, argv, "rviz", ros::init_options::AnonymousName);
 
    startContinueChecker();
    
/// ...
// L182 - 189
    if (!ros::master::check())
    {
      WaitForMasterDialog dialog;
      if (dialog.exec() != QDialog::Accepted)
      {
        return false;
      }
    }
 
// ...
 
#if CATCH_EXCEPTIONS
  }
  catch (std::exception& e)
  {
    ROS_ERROR("Caught exception while loading: %s", e.what());
    return false;
  }
#endif
  return true;
}

하지만 master가 없을 때 기다리는 창과, 있을 때 로딩을 기다리는 화면은 다르게 나타난다.

<master가 없어 기다리는 경우>

<master가 있어 로딩을 기다리는 화면, 실제로는 아래 버전이 표기된다>

실제로 wait_for_master_dialog.cpp를 확인해봐도 WaitForMasterDialog는 아래와 같은 텍스트를 내도록 되어 있다.

WaitForMasterDialog::WaitForMasterDialog(QWidget* parent) : QMessageBox(parent)
{
  setIcon(QMessageBox::Critical);
 
  const std::string& master_uri = ros::master::getURI();
  std::stringstream ss;
  ss << "Could not contact ROS master at [" << master_uri << "], retrying...";
 
  setText(QString::fromStdString(ss.str()));
  setWindowTitle("RViz: waiting for master");
  setStandardButtons(QMessageBox::Cancel);
 
  QTimer* timer = new QTimer(this);
  connect(timer, &QTimer::timeout, this, &WaitForMasterDialog::onTimer);
  timer->start(1000);
}

그 다음 부분을 살펴보면 VisualizationFrame-d filename으로 불러오는 config를 사용하는 라인을 확인할 수 있다.

/// ...
// L204 - 222
    frame_ = new VisualizationFrame();
    if (!help_path.empty())
    {
      frame_->setHelpPath(QString::fromStdString(help_path));
    }
    frame_->setShowChooseNewMaster(vm.count("in-mc-wrapper") > 0);
    if (vm.count("splash-screen"))
      frame_->setSplashPath(QString::fromStdString(splash_path));
 
    frame_->initialize(QString::fromStdString(display_config));
 
    if (!fixed_frame.empty())
      frame_->getManager()->setFixedFrame(QString::fromStdString(fixed_frame));
 
    frame_->getManager()->getSelectionManager()->setDebugMode(vm.count("verbose") > 0);
 
    if (vm.count("fullscreen"))
      frame_->setFullScreen(true);
    frame_->show();
/// ...

따라서 VisualizationFrame의 메소드인 initialize()show()를 살펴보았다.

visualization_frame.cpp

/// ...
// L242 - 379
void VisualizationFrame::initialize(const QString& display_config_file)
{
  initConfigs();
 
  loadPersistentSettings();
 
  QIcon app_icon(QString::fromStdString((fs::path(package_path_) / "icons/package.png").string()));
  setWindowIcon(app_icon);
 
  if (splash_path_ != "")
  {
    QPixmap splash_image(splash_path_);
    splash_ = new SplashScreen(splash_image);
    splash_->show();
    connect(this, &VisualizationFrame::VisualizationFrame::statusUpdate, splash_,
            [this](const QString& message) { splash_->showMessage(message); });
  }
  Q_EMIT statusUpdate("Initializing");

위에서 첨부된 로딩 이미지를 보여주는 곳은 splash_screen.cpp 이다. 따라서 이 부근에서 멈춘것으로 추정.

  // Periodically process events for the splash screen.
  // See: http://doc.qt.io/qt-5/qsplashscreen.html#details
  QCoreApplication::processEvents();
 
  if (!ros::isInitialized())
  {
    int argc = 0;
    ros::init(argc, nullptr, "rviz", ros::init_options::AnonymousName);
  }
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  QWidget* central_widget = new QWidget(this);
  QHBoxLayout* central_layout = new QHBoxLayout;
  central_layout->setSpacing(0);
  central_layout->setMargin(0);
 
  render_panel_ = new RenderPanel(central_widget);
 
  hide_left_dock_button_ = new QToolButton();
  hide_left_dock_button_->setContentsMargins(0, 0, 0, 0);
  hide_left_dock_button_->setArrowType(Qt::LeftArrow);
  hide_left_dock_button_->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding));
  hide_left_dock_button_->setFixedWidth(16);
  hide_left_dock_button_->setAutoRaise(true);
  hide_left_dock_button_->setCheckable(true);
 
  connect(hide_left_dock_button_, &QToolButton::toggled, this,
          &VisualizationFrame::VisualizationFrame::hideLeftDock);
 
  hide_right_dock_button_ = new QToolButton();
  hide_right_dock_button_->setContentsMargins(0, 0, 0, 0);
  hide_right_dock_button_->setArrowType(Qt::RightArrow);
  hide_right_dock_button_->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding));
  hide_right_dock_button_->setFixedWidth(16);
  hide_right_dock_button_->setAutoRaise(true);
  hide_right_dock_button_->setCheckable(true);
 
  connect(hide_right_dock_button_, &QToolButton::toggled, this,
          &VisualizationFrame::VisualizationFrame::hideRightDock);
 
  central_layout->addWidget(hide_left_dock_button_, 0);
  central_layout->addWidget(render_panel_, 1);
  central_layout->addWidget(hide_right_dock_button_, 0);
 
  central_widget->setLayout(central_layout);
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  initMenus();
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  initToolbars();
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  setCentralWidget(central_widget);
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  manager_ = new VisualizationManager(render_panel_, this);
  manager_->setHelpPath(help_path_);
  connect(manager_, &VisualizationManager::escapePressed, this,
          &VisualizationFrame::VisualizationFrame::exitFullScreen);
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  render_panel_->initialize(manager_->getSceneManager(), manager_);
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  ToolManager* tool_man = manager_->getToolManager();
 
  connect(manager_, &VisualizationManager::configChanged, this,
          &VisualizationFrame::setDisplayConfigModified);
  connect(tool_man, &ToolManager::toolAdded, this, &VisualizationFrame::VisualizationFrame::addTool);
  connect(tool_man, &ToolManager::toolRemoved, this, &VisualizationFrame::VisualizationFrame::removeTool);
  connect(tool_man, &ToolManager::toolRefreshed, this,
          &VisualizationFrame::VisualizationFrame::refreshTool);
  connect(tool_man, &ToolManager::toolChanged, this,
          &VisualizationFrame::VisualizationFrame::indicateToolIsCurrent);
 
  manager_->initialize();
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  if (display_config_file != "")
  {
    loadDisplayConfig(display_config_file);
  }
  else
  {
    loadDisplayConfig(QString::fromStdString(default_display_config_file_));
  }
 
  // Periodically process events for the splash screen.
  QCoreApplication::processEvents();
 
  delete splash_;
  splash_ = nullptr;
 
  manager_->startUpdate();
  initialized_ = true;
  Q_EMIT statusUpdate("RViz is ready.");
 
  connect(manager_, &VisualizationManager::preUpdate, this,
          &VisualizationFrame::VisualizationFrame::updateFps);
  connect(manager_, &VisualizationManager::statusUpdate, this,
          &VisualizationFrame::VisualizationFrame::statusUpdate);
}
/// ...

추후 --verbose 옵션으로 재구성하여 구체적으로 디버깅해야할 것으로 보임.

$ rviz -v -l -d local_planner.rviz 
[ INFO] [1693831893.782386822]: rviz version 1.14.20
[ INFO] [1693831893.782429393]: compiled against Qt version 5.12.8
[ INFO] [1693831893.782445825]: compiled against OGRE version 1.9.0 (Ghadamon)
[ INFO] [1693831894.233156552]: Forcing OpenGl version 0.****
 
# ...중략
 
[ INFO] [1693831894.680779705]: Creating resources for group General
[ INFO] [1693831894.680812620]: All done
[ INFO] [1693831894.680846172]: Parsing scripts for resource group Internal
[ INFO] [1693831894.680903187]: Finished parsing scripts for resource group Internal
[ INFO] [1693831894.680942568]: Creating resources for group Internal
[ INFO] [1693831894.680974183]: All done
[ INFO] [1693831894.685315669]: GLRenderSystem::_createRenderWindow "OgreWindow(1)", 100x30 windowed  miscParams: FSAA=4 contentScalingFactor=1.000000 externalGLControl= externalWindowHandle=90177559 parentWindowHandle=90177559 
[ INFO] [1693831894.704779858]: GLXWindow::create used FBConfigID = 679
[ INFO] [1693831895.316704233]: Mesh: Loading rviz_sphere.mesh.
[ INFO] [1693831895.321399517]: Texture: SelectionRect0Texture: Loading 1 faces(PF_R8G8B8A8,1x1x1) with 0 generated mipmaps from Image. Internal format is PF_A8R8G8B8,1x1x1.
[ INFO] [1693831895.323751557]: Mesh: Loading rviz_cylinder.mesh.
[ INFO] [1693831895.324086426]: Mesh: Loading rviz_cone.mesh.
[ INFO] [1693831895.488769218]: Texture: ROSImageTexture0: Loading 1 faces(PF_L8,420x300x1) with 0 generated mipmaps from Image. Internal format is PF_L8,420x300x1.
[ INFO] [1693831903.964006272]: GLRenderSystem::_createRenderWindow "OgreWindow(2)", 640x480 windowed  miscParams: FSAA=4 contentScalingFactor=1.000000 externalGLControl= externalWindowHandle=90177806 parentWindowHandle=90177806 

구체적으로는 여기 부분에서 로딩이 오래걸렸다.

[ INFO] [1693832099.039088358]: GLRenderSystem::_createRenderWindow "OgreWindow(1)", 100x30 windowed  miscParams: FSAA=4 contentScalingFactor=1.000000 externalGLControl= externalWindowHandle=90177567 parentWindowHandle=90177567 
[ INFO] [1693832099.057570928]: GLXWindow::create used FBConfigID = 679
[ INFO] [1693832101.717091598]: Mesh: Loading rviz_sphere.mesh.
[ INFO] [1693832101.722365199]: Texture: SelectionRect0Texture: Loading 1 faces(PF_R8G8B8A8,1x1x1) with 0 generated mipmaps from Image. Internal format is PF_A8R8G8B8,1x1x1.
[ INFO] [1693832101.725878425]: Mesh: Loading rviz_cylinder.mesh.
[ INFO] [1693832101.726298203]: Mesh: Loading rviz_cone.mesh.
[ INFO] [1693832102.044695764]: Texture: ROSImageTexture0: Loading 1 faces(PF_L8,420x300x1) with 0 generated mipmaps from Image. Internal format is PF_L8,420x300x1.

그렇다면 마지막 파트인 아래 부분이 실행 중인 것으로 추측한다.

[ INFO] [1693831903.964006272]: GLRenderSystem::_createRenderWindow "OgreWindow(2)", 640x480 windowed  miscParams: FSAA=4 contentScalingFactor=1.000000 externalGLControl= externalWindowHandle=90177806 parentWindowHandle=90177806 

Incorporating RViz into a Custom GUI

Reference

추후 GCS용 RViz를 제작하기 위해서 rviz를 overwrapping한 Qt를 제작해봐도 좋을 것 같다.

a Custom GUI RViz

  • : Incorporating RViz into a Custom GUI