2

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

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

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

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

所以,总结一下:在不影响性能的情况下,创建多个显示不同 OpenSceneGraph 场景的 Qt 小部件的最佳方法是什么?

我们已经尝试过的:

  • 我们已经考虑过使用单个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;
    };

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

对我们来说理想的解决方案是一个小部件,它只在用户与之交互时触发重绘请求,例如通过单击双击滚动等。更准确地说,这个小部件应该保持空闲状态,直到需要更新。有没有可能发生类似的事情?我们欢迎任何建议。

4

4 回答 4

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 示例有时缺乏细节,但源从不撒谎……)
于 2012-09-04T08:15:38.700 回答
0

这应该是可能的。我不敢相信他们使用了一个以 100Hz 运行的计时器来更新小部件——这确实不是正确的做法。

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

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

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

于 2012-06-08T12:41:48.307 回答
0

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

通过打开渲染统计(在查看器上按“s”),我发现“慢”实际上不是渲染造成的,实际上渲染是快的。

查看器“慢”的原因是许多gui事件没有处理。比如你拖拽场景,很多拖拽事件是Qt生成的,但是只有少数几个会传递给OSG查看器,所以查看器响应“慢”。

事件下降实际上是由于渲染太快......在Viewer::eventTraversal()中,只处理那些相对新的事件,而“相对新的”通过cutOffTime = _frameStamp->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;
}
于 2013-12-03T04:53:32.627 回答
0

我也花了很多时间来弄清楚如何完成这项工作。

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

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

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

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

于 2014-01-20T17:06:39.907 回答