我们目前面临以下问题:我们有一个应用程序需要在不同的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小部件进行交互。
我们的理想解决方案是一个小部件,只有在用户与之交互时才会触发重绘请求,例如点击,双击,滚动等。更准确地说,此小部件应保持空闲,直到需要更新为止。类似于这个可能的东西?我们欢迎任何建议。
答案 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()
并没有什么坏处。否则,只有在窗口小部件需要重新绘制时,查看器才会刷新。
简而言之,这就是我所学到的:
答案 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
)