Gnosophilon
Gnosophilon

Reputation: 1369

Multiple Qt widgets depicting different OpenSceneGraph nodes without performance loss

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:

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.

Upvotes: 2

Views: 2336

Answers (4)

Rtbo
Rtbo

Reputation: 230

I spent also quite some time to figure out how to make this work.

I think that the answer from Gnosophilon cannot work with Qt5, as the rules for switching context thread are more strict than with Qt4 (ie, a call to moveToThread() is required on the OpenGL context object). At time of writing, OSG doesn't satisfy this rules. (At least I couldn't make it work)

I haven't figure out how to do it in a separate thread, however, to render the scene smoothly in the UI thread, without use of fixed interval timer, one may do the following

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

osgQt::setViewer() handle with global variables, so only one viewer at a time can be used. (which can be a CompositeViewer of course)

Upvotes: 0

lostli
lostli

Reputation: 1

I met similar problem when I have multiple OSG viewers in one Qt application, where one of them works well, while the rest are very "slow".

By turning on the rendering stats (press "s" on the viewer), I found that the "slow" actually is not caused by the rendering, actually the rendering is fast.

The reason why the viewers are "slow" is that many gui events are not handled. For example, whey you drag the scene, many drag events are generated by Qt, but only few are passed to the OSG viewer, so the viewers response "slowly".

The events dropping actually is due to that the rendering is too fast...in Viewer::eventTraversal(), only those relative new events are processed, and the "relative new" is measured by cutOffTime = _frameStamp->getReferenceTime(). So if Qt generates the events slower than the rendering, many events will be cut off, and thus not processed.

And finally, after I found the root cause, the solution is easy. Let's cheat a bit on the reference time of _frameStamp used in the 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;
}

Upvotes: 0

Gnosophilon
Gnosophilon

Reputation: 1369

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 an osgViewer::ViewerBase object and simply calls viewer->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:

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

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 CameraManipulator class, it doesn't hurt to call viewer->requestRedraw() after changing viewer settings. Else, the viewer will only refresh when the widget requires a repaint.

In short, here's what I learned:

  • Don't use timers for rendering
  • Don't give up on multiple threads just yet
  • Nothing beats reading the source code (the official OSG examples were sometimes scarce on details, but the source never lies...)

Upvotes: 1

It should be possible. I can't believe they used a timer running at 100Hz to update the widgets -- it's really not the right way to do it.

I don't know about OSG's architecture, but you need to figure a way to obtain a callback from OSG when the data has been changed. In the callback, you simply queue an update event to the appropriate widget like so:

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

This callback code is thread safe and you can invoke it in any thread, whether it has been started by QThread or not. The event will be compressed, that means that it acts like a flag. Posting it sets the flag, and the flag will be reset when the widget finishes with the update. Posting it multiple times when the update is pending does not add any events to the event queue, and does not imply multiple updates.

Upvotes: 0

Related Questions