We are currently facing the following problem: We have an application that needs to display a multitude of separate OpenSceneGraph scenes in different Qt widgets. For example, we might have one Qt widget depicting a sphere, while another widget depicts an icosahedron. Since we are using OpenSceneGraph 3.0.1, we followed the osgViewerQt example from the official documentation for implementing this.
The example code uses a QTimer in order to force updates for the viewer widget:
connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );
_timer.start( 10 );
The problems now begin when we want to create and show multiple widgets. Since each widget comes with its own timer, performance rapidly decreases with the number of open widgets. Not only is the interaction with the OSG widgets very slow, also the interaction with other Qt widgets noticeably lags. Even a halfway recent quad-core system is almost overwhelmed when approximately 5 windows are open. This issue is definitely not related to our graphics hardware. Other applications may render much larger scenes (Blender, Meshlab etc.) without any negative performance impact.
So, to summarize: What would be the best way of creating multiple Qt widgets showing different OpenSceneGraph scenes without a performance impact?
What we already tried:
- We already considered using a single
osgViewer::CompositeViewerfor
rendering all scene objects. However, we discarded this idea for
now because it will probably make interactions with a single widget
very complicated. - We tried putting the rendering portion of each
osgViewer::CompositeViewerin a separate thread as detailed by the osgQtWidgets example.
Our second try (using threads) looked roughly like this:
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;
};
However, this also resulted in a remarkable performance decrease. Each thread still requires much CPU time (which is not surprising as the basic interaction is still handled with a timer). The only advantage of this approach is that at least interaction with other Qt widgets remain possible.
The ideal solution for us would be a widget that only fires redraw requests whenever the user interacts with it, for example by clicking, double-clicking, scrolling etc. More precisely, this widget should remain idle until there is a need for an update. Is something akin to this possible at all? We would welcome any suggestions.
Having tried out several models for this problem, I am happy to report that I found one that is working perfectly. I am using a
QThread(similar to the thread described above) that essentially wraps anosgViewer::ViewerBaseobject and simply callsviewer->run().The trick to keep CPU usage low is to force OpenSceneGraph to render on demand only. Having tried out the various options, I found the following two settings to work best:
A viewer that is modified like this will not use spurious CPU cycles for continuous updates while still using multiple threads for culling and drawing. Other threading models might of course perform better in some cases, but for me, this was sufficient.
If any one else attempts a similar solution, be warned that some operations now require explicit redraw requests. For example, when handling interactions with OSG objects or when you are writing your own
CameraManipulatorclass, it doesn’t hurt to callviewer->requestRedraw()after changing viewer settings. Else, the viewer will only refresh when the widget requires a repaint.In short, here’s what I learned: