多个Qt小部件描述了不同的OpenSceneGraph节点而没有性能损失

时间:2012-06-05 08:25:35

标签: c++ qt4 openscenegraph

我们目前面临以下问题:我们有一个应用程序需要在不同的Qt小部件中显示多个单独的OpenSceneGraph场景。例如,我们可能有一个Qt小部件描绘一个球体,而另一个小部件描绘一个二十面体。由于我们使用的是OpenSceneGraph 3.0.1,因此我们遵循osgViewerQt example from the official documentation来实现此目的。

示例代码使用QTimer来强制更新查看器小部件:

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

当我们想要创建和显示多个小部件时,问题就开始了。由于每个小部件都有自己的计时器,因此性能会随着打开小部件的数量而迅速降低。与OSG小部件的交互不仅非常缓慢,而且与其他 Qt小部件的交互也明显滞后。当大约5个窗口打开时,即使是最近中途的四核系统也几乎不堪重负。此问题肯定与我们的图形硬件无关。其他应用程序可能会渲染更大的场景(Blender,Meshlab等),而不会对造成任何负面影响。

因此,总结一下:创建多个Qt小部件的最佳方法是显示不同的OpenSceneGraph场景而不会影响性能?

我们已经尝试过:

  • 我们已经考虑过使用单个osgViewer::CompositeViewer 渲染所有场景对象。但是,我们放弃了这个想法 现在因为它可能会与单个小部件进行交互 非常复杂。
  • 我们尝试将每个osgViewer::CompositeViewer的呈现部分放在一个单独的线程中,详见osgQtWidgets example

我们的第二次尝试(使用线程)看起来大致如下:

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

然而,此导致性能显着下降。每个线程仍然需要很多CPU时间(这并不奇怪,因为基本交互仍然使用计时器处理)。这种方法的唯一优势是至少可以与其他 Qt小部件进行交互。

我们的理想解决方案是一个小部件,只有在用户与之交互时才会触发重绘请求,例如点击双击滚动等。更准确地说,此小部件应保持空闲,直到需要更新为止。类似于这个可能的东西?我们欢迎任何建议。

4 个答案:

答案 0 :(得分:1)

我已经尝试了几个模型来解决这个问题,我很高兴地报告说我找到了一个完美的模型。我使用QThread(类似于上面描述的主题)基本上包装osgViewer::ViewerBase对象并简单地调用viewer->run()

保持CPU使用率低的技巧是强制OpenSceneGraph仅按需呈现 。尝试了各种选项后,我发现以下两种设置效果最佳:

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

如此修改的查看器将使用虚假CPU周期进行连续更新,同时仍使用多个线程进行剔除和绘图。其他线程模型当然可能在某些情况下表现更好,但对我来说,这已经足够了。

如果其他人尝试使用类似的解决方案,请注意某些操作现在需要显式重绘请求。例如,在使用OSG对象处理交互时或者在编写自己的CameraManipulator类时,更改查看器设置后调用viewer->requestRedraw()并没有什么坏处。否则,只有在窗口小部件需要重新绘制时,查看器才会刷新。

简而言之,这就是我所学到的:

  • 不要使用计时器进行渲染
  • 不要放弃多线程
  • 没有什么能比阅读源代码了(官方的OSG示例有时候细节很少,但来源永远不会......)

答案 1 :(得分:0)

应该可以。我无法相信他们使用以100Hz运行的计时器来更新小部件 - 这真的不是正确的方法。

我不了解OSG的架构,但是当数据发生变化时,你需要想办法从OSG获得回调。在回调中,您只需将更新事件排队到适当的窗口小部件,如下所示:

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

此回调代码是线程安全的,您可以在任何线程中调用它,无论它是否已由QThread启动。事件将被压缩,这意味着它就像一个标志。发布它会设置标志,当小部件完成更新时,将重置标志。在更新处于挂起状态时多次发布不会向事件队列添加任何事件,也不会意味着多次更新。

答案 2 :(得分:0)

当我在一个Qt应用程序中有多个OSG查看器时,我遇到了类似的问题,其中一个运行良好,而其余的都非常“慢”。

通过打开渲染统计数据(在查看器上按“s”),我发现“慢”实际上不是由渲染引起的,实际上渲染速度很快。

观众“慢”的原因是没有处理很多gui事件。例如,滚动场景时,Qt会生成许多拖动事件,但只有少数会传递给OSG查看器,因此观众会“慢慢”响应。

实际丢弃的事件是由于渲染太快......在Viewer :: eventTraversal()中,只处理那些相对新事件,并且cutOffTime = _frameStamp-&gt;测量“相对新”。 getReferenceTime()。因此,如果Qt生成的事件比渲染慢,那么许多事件将被切断,因此不会被处理。

最后,在找到根本原因后,解决方案很简单。让我们在Viewer :: eventTraversal()中使用的_frameStamp的参考时间上作弊:

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

答案 3 :(得分:0)

我花了很长时间才弄清楚如何使这项工作。

我认为Gnosophilon的答案不适用于Qt5,因为切换上下文线程的规则比Qt4更严格(即,在OpenGL上下文对象上需要调用moveToThread())。在撰写本文时,OSG不满足此规则。 (至少我不能使它工作)

我还没弄清楚如何在一个单独的线程中做到这一点,但是,要在UI线程中平滑渲染场景,而不使用固定间隔计时器,可以执行以下操作

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

osgQt::setViewer()处理全局变量,因此一次只能使用一个查看器。 (当然可以是CompositeViewer