Несколько виджетов Qt, изображающих разные узлы OpenSceneGraph без потери производительности

В настоящее время мы столкнулись со следующей проблемой: у нас есть приложение, которому необходимо отображать множество отдельных сцен OpenSceneGraph в разных виджетах Qt. Например, у нас может быть один виджет Qt, изображающий сферу, а другой — икосаэдр. Поскольку мы используем OpenSceneGraph 3.0.1, мы следовали пример osgViewerQt из официальной документации для реализации этого.

В примере кода используется QTimer для принудительного обновления виджета средства просмотра:

    connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );
    _timer.start( 10 );

Теперь проблемы начинаются, когда мы хотим создать и отобразить несколько виджетов. Поскольку у каждого виджета есть собственный таймер, производительность быстро снижается с увеличением количества открытых виджетов. Взаимодействие с виджетами OSG не только очень медленное, но и взаимодействие с другими виджетами Qt заметно отстает. Даже недавняя четырехъядерная система почти перегружена, когда открыто примерно 5 окон. Эта проблема определенно не связана с нашим графическим оборудованием. Другие приложения могут отображать гораздо большие сцены (Blender, Meshlab и т. д.) без какого-либо негативного влияния на производительность.

Итак, подведем итог: как лучше всего создать несколько виджетов Qt, показывающих разные сцены OpenSceneGraph без влияния на производительность?

Что мы уже пробовали:

  • Мы уже рассматривали возможность использования одного osgViewer::CompositeViewer для рендеринга всех объектов сцены. Однако мы пока отказались от этой идеи, потому что она, вероятно, сильно усложнит взаимодействие с одним виджетом.
  • Мы попытались поместить часть рендеринга каждого osgViewer::CompositeViewer в отдельный поток, как указано в пример osgQtWidgets.

Наша вторая попытка (с использованием потоков) выглядела примерно так:

   class ViewerFrameThread : public OpenThreads::Thread
    {
        public:
            ViewerFrameThread(osgViewer::ViewerBase* viewerBase):
                _viewerBase(viewerBase) {}

            ~ViewerFrameThread()
            {
                cancel();
                while(isRunning())
                {
                    OpenThreads::Thread::YieldCurrentThread();
                }
            }

            int cancel()
            {
                _viewerBase->setDone(true);
                return 0;
            }

            void run()
            {
                int result = _viewerBase->run();
            }

            osg::ref_ptr<osgViewer::ViewerBase> _viewerBase;
    };

Однако это также привело к значительному снижению производительности. Каждый поток по-прежнему требует много процессорного времени (что неудивительно, поскольку основное взаимодействие по-прежнему обрабатывается таймером). Единственное преимущество этого подхода заключается в том, что по крайней мере остается возможным взаимодействие с другими виджетами Qt.

Идеальным решением для нас был бы виджет, который запускает запросы на перерисовку только тогда, когда пользователь взаимодействует с ним, например, щелкнув, двойной щелчок, прокрутив. em> и т. д. Точнее, этот виджет должен оставаться бездействующим до тех пор, пока не возникнет необходимость в обновлении. Возможно ли что-то подобное этому вообще? Будем рады любым предложениям.


person Gnosophilon    schedule 05.06.2012    source источник


Ответы (4)


Опробовав несколько моделей для решения этой проблемы, я рад сообщить, что нашел ту, которая отлично работает. Я использую QThread (похожий на поток, описанный выше), который по существу обертывает объект osgViewer::ViewerBase и просто вызывает viewer->run().

Хитрость, позволяющая снизить нагрузку на ЦП, заключается в том, чтобы заставить OpenSceneGraph выполнять рендеринг только по запросу. Попробовав различные варианты, я обнаружил, что следующие два параметра работают лучше всего:

viewer->setRunFrameScheme( osgViewer::ViewerBase::ON_DEMAND );
viewer->setThreadingModel( osgViewer::ViewerBase::CullDrawThreadPerContext );

Средство просмотра, измененное таким образом, не будет использовать ложные циклы ЦП для непрерывных обновлений, но при этом использовать несколько потоков для отбраковки и рисования. Конечно, в некоторых случаях другие модели многопоточности могут работать лучше, но для меня этого было достаточно.

Если кто-то еще попытается применить подобное решение, имейте в виду, что некоторые операции теперь требуют явных запросов на перерисовку. Например, при обработке взаимодействий с объектами OSG или при написании собственного класса CameraManipulator не помешает вызвать viewer->requestRedraw() после изменения настроек средства просмотра. В противном случае средство просмотра будет обновляться только тогда, когда виджет требует перерисовки.

Короче, вот что я узнал:

  • Не используйте таймеры для рендеринга
  • Пока не отказывайтесь от нескольких потоков
  • Ничто не сравнится с чтением исходного кода (официальные примеры OSG иногда были скудны по деталям, но источник никогда не врёт...)
person Gnosophilon    schedule 04.09.2012

Это должно быть возможно. Я не могу поверить, что они использовали таймер, работающий на частоте 100 Гц, для обновления виджетов — это действительно неправильный способ сделать это.

Я не знаю об архитектуре OSG, но вам нужно придумать способ получить обратный вызов от OSG, когда данные были изменены. В обратном вызове вы просто ставите в очередь событие обновления для соответствующего виджета, например:

void OSGCallbackForWidgetA()
{
  QCoreApplication::postEvent(widgetA, new QEvent(QEvent::UpdateRequest),
                              Qt::LowEventPriority);
}

Этот код обратного вызова является потокобезопасным, и вы можете вызывать его в любом потоке, независимо от того, был ли он запущен QThread или нет. Событие будет сжато, это означает, что оно действует как флаг. Его публикация устанавливает флаг, и этот флаг будет сброшен, когда виджет завершит обновление. Публикация его несколько раз, когда ожидается обновление, не добавляет никаких событий в очередь событий и не подразумевает множественных обновлений.

person Kuba hasn't forgotten Monica    schedule 08.06.2012

Я столкнулся с подобной проблемой, когда у меня есть несколько просмотрщиков OSG в одном приложении Qt, где один из них работает хорошо, а остальные очень «медленные».

Включив статистику рендеринга (нажмите «s» в средстве просмотра), я обнаружил, что «медленный» на самом деле не вызван рендерингом, на самом деле рендеринг происходит быстро.

Причина, по которой средства просмотра «медленные», заключается в том, что многие события графического интерфейса не обрабатываются. Например, когда вы перетаскиваете сцену, Qt генерирует много событий перетаскивания, но лишь немногие передаются средству просмотра OSG, поэтому средства просмотра реагируют «медленно».

На самом деле отбрасывание событий происходит из-за того, что рендеринг слишком быстрый... в Viewer::eventTraversal() обрабатываются только эти относительные новые события, а "относительное новое" измеряется cutOffTime = _frameStamp->getReferenceTime(). Поэтому, если Qt генерирует события медленнее, чем рендеринг, многие события будут обрезаны и, следовательно, не будут обработаны.

И, наконец, после того, как я нашел основную причину, решение легко. Давайте немного схитрим с эталонным временем _frameStamp, используемым в Viewer::eventTraversal():

class MyViewer : public osgViewer::Viewer
{
  ...
  virtual void eventTraversal();
  ...
}
void MyViewer::eventTraversal()
{
  double reference_time = _frameStamp->getReferenceTime();
  _frameStamp->setReferenceTime(reference_time*100);

  osgViewer::Viewer::eventTraversal();

  _frameStamp->setReferenceTime(reference_time);

  return;
}
person lostli    schedule 03.12.2013

Я также потратил довольно много времени, чтобы выяснить, как заставить это работать.

Я думаю, что ответ от Gnosophilon не может работать с Qt5, так как правила переключения потока контекста более строгие, чем с Qt4 (т.е. требуется вызов moveToThread() для объекта контекста OpenGL). На момент написания OSG не удовлетворяет этим правилам. (По крайней мере, у меня не получилось)

Я не понял, как это сделать в отдельном потоке, однако для плавного рендеринга сцены в потоке пользовательского интерфейса без использования таймера с фиксированным интервалом можно сделать следующее.

viewer_->setRunFrameScheme(osgViewer::ViewerBase::ON_DEMAND);
viewer_->setThreadingModel(osgViewer::CompositeViewer::SingleThreaded);
osgQt::initQtWindowingSystem();
osgQt::setViewer(viewer_.get());

osgQt::setViewer() обрабатывать глобальные переменные, поэтому одновременно можно использовать только одно средство просмотра. (что может быть CompositeViewer, конечно)

person Rtbo    schedule 20.01.2014