[osg-users] OSG 3.2.1 and Qt5 Widget integration

157 views
Skip to first unread message

Can Olcek

unread,
Jul 22, 2015, 8:06:17 AM7/22/15
to osg-...@lists.openscenegraph.org
Hi,

I have been working Qt5 integration for my current rendering application implementing deferred rendering and came up with couple of solutions. I want to share it with the people struggling Qt5 integration while waiting official stable release :)

Since the current stable release is OSG 3.2.1, this will be based on that version.

For Qt5 version, I recommend using >= 5.4, because in earlier versions you have to do a lot by yourself. In 5.4, at least you have QOpenGLWidget.

Even though I will give solution for widget, this can be applied to QWindow solution as well. The codes will be bits and pieces, unfortunately cannot share full working code.

Firstly, create a new widget rendering class subclassing QOpenGLWidget. This one is almost same as the QGLWidget version of it.


Code:

class RenderWidget : public [b]QOpenGLWidget[/b]
{
Q_OBJECT

public:
RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);
~RenderWidget();

protected:
virtual void initializeGL();
virtual void paintGL();
virtual void resizeGL(int width, int height);

osg::ref_ptr<osgViewer::GraphicsWindow> gw;
osg::ref_ptr<osgViewer::Viewer> viewer;

private:
QTimer heartbeat;
};

RenderWidget::RenderWidget(QWidget* parent, Qt::WindowFlags f)
{
// instead of osgViewer::setUpViewerAsEmbeddedInWindow, we are going to
// inject our osg::State subclass
gw = new GraphicsWindowEx(0, 0, width(), height());
viewer = new osgViewer::Viewer();
viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);

// setup viewer's camera etc.
// In my case, I don't want the base camera to clear anything
// I have a lot of other cameras queued as FBO rendering
viewer->getCamera()->setViewport(0, 0, width(), height())
viewer->getCamera()->setGraphicsContext(gw);
viewer->getCamera()->setClearMask(0);
//...

connect(&heartbeat, SIGNAL(timeout()), this, SLOT(update()), Qt::QueuedConnection);
hearbeat.start(10);
}

void RenderWidget::initializeGL()
{
viewer->realize();
}

void RenderWidget::paintGL()
{
static_cast<StateEx *>(&state)->setDefaultFbo(defaultFramebufferObject());

viewer->frame();

// OR if you want to mix OSG with Qt 2D API

QPainter painter(this);
painter.beginNativePainting();
viewer->frame();
painter.endNativePainting();

// calculate fps...
painter.setPen(Qt::white);
painter.drawText(width() - 100, 10, 50, 25, Qt::AlignLeft, QString::number(fps));
painter.end();
}

void RenderWidget::resizeGL(int width, int height)
{
gw->getEventQueue()->windowResize(0, 0, width, height);
gw->resized(0, 0, width, height);
//...
}




The difference between old QGLWidget and QOpenGLWidget is how they handle the rendering in the background. QOpenGLWidget is using QOffscreenSurface and QFrameBufferObject to render its content. The main problem of the current OSG integration is that it does not expect a superior FBO as main framebuffer. Like in my case, if you are using a lot of FBOs, some point OSG unbinds them and returns to direct drawing or leaves the last FBO bound after drawing. However, it should return(bind) to our superior FBO used by QOpenGLWidget.

Let me explain it with the source code of OSG.


Code:

void RenderStage::drawInner(osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous, bool& doCopyTexture)
{
//...

osg::State& state = *renderInfo.getState();

osg::FBOExtensions* fbo_ext = _fbo.valid() ? osg::FBOExtensions::instance(state.getContextID(),true) : 0;
bool fbo_supported = fbo_ext && fbo_ext->isSupported();

bool using_multiple_render_targets = fbo_supported && _fbo->hasMultipleRenderingTargets();

if (!using_multiple_render_targets)
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE)

if( getDrawBufferApplyMask() )
glDrawBuffer(_drawBuffer);

if( getReadBufferApplyMask() )
glReadBuffer(_readBuffer);

#endif
}

if (fbo_supported)
{
_fbo->apply(state);
}

RenderBin::draw(renderInfo,previous);

//...
}




As you can see, _fbo->apply(state); is the only point where FBO of the camera (which comes from our osg::Camera and RenderStage::runCameraSetUp) is bound before drawing our geometry etc. However, there is no line to handle returning back to FBO of QOpenGLWidget. Even we put a empty FBO as a last camera, it will executes following line:


Code:

void FrameBufferObject::apply(State &state, BindTarget target) const
{
//...

if (_attachments.empty())
{
ext->glBindFramebuffer(target, 0);
return;
}

//...
}




So basicly, it switches back to direct rendering because of 0 argument. Therefore, we have to inject default FBO of QOpenGLWidget somehow. You might already notice two special lines in paintGL() and constructor methods of RenderWidget implementation. We are going to subclass osg::State and osgViewer::GraphicsWindow. Here are the classes:


Code:


class StateEx : public osg::State
{
public:
StateEx();

inline void setDefaultFbo(GLuint fbo);
inline GLuint getDefaultFbo() const;

protected:
GLuint defaultFbo;
};

StateEx::StateEx() :
defaultFbo(0)
{
}

inline void StateEx::setDefaultFbo(GLuint fbo)
{
defaultFbo = fbo;
}

inline GLuint getDefaultFbo() const
{
return defaultFbo;
}




Code:

class GraphicsWindowEx : public osgViewer::GraphicsWindow
{
public:
GraphicsWindowEx(osg::GraphicsContext::Traits* traits);
GraphicsWindowEx(int x, int y, int width, int height);

void init();

virtual bool isSameKindAs(const osg::Object* object) const { return dynamic_cast<const GraphicsWindowEx *>(object) != 0; }
virtual const char* libraryName() const { return ""; }
virtual const char* className() const { return "GraphicsWindowEx"; }

// dummy implementations, assume that graphics context is *always* current and valid.
virtual bool valid() const { return true; }
virtual bool realizeImplementation() { return true; }
virtual bool isRealizedImplementation() const { return true; }
virtual void closeImplementation() {}
virtual bool makeCurrentImplementation() { return true; }
virtual bool releaseContextImplementation() { return true; }
virtual void swapBuffersImplementation() {}
virtual void grabFocus() {}
virtual void grabFocusIfPointerInWindow() {}
virtual void raiseWindow() {}
};

GraphicsWindowEx::GraphicsWindowEx(osg::GraphicsContext::Traits* traits)
{
_traits = traits;

init();
}

GraphicsWindowEx::GraphicsWindowEx(int x, int y, int width, int height);
{
_traits = new osg::GraphicsContext::Traits();
_traits->x = x;
_traits->x = y;
_traits->width = width;
_traits->height = height;

init();
}

void GraphicsWindowEx::init()
{
if(valid())
{
// inject our "extended" state
setState(new StateEx());
getState()->setGraphicsContext(this);

if (_traits.valid() && _traits->sharedContext.valid())
{
getState()->setContextID(_traits->sharedContext->getState()->getContextID() );
incrementContextIDUsageCount(getState()->getContextID());
}
else
{
getState()->setContextID(osg::GraphicsContext::createNewContextID());
}
}
}




Now, our rendering knows the default FBO of the QOpenGLWidget so with custom RenderStage we can use this information. Only method we have to override is drawInner.


Code:

void RenderStageEx::drawInner(osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous, bool& doCopyTexture)
{
//...
osg::State& state = *renderInfo.getState();

osg::FBOExtensions* fbo_ext = osg::FBOExtensions::instance(state.getContextID(), true);
bool fbo_supported = fbo_ext && fbo_ext->isSupported();

if (fbo_supported)
{
if(_fbo.valid())
{
if (!_fbo->hasMultipleRenderingTargets())
{
#if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE)

if( getDrawBufferApplyMask() )
glDrawBuffer(_drawBuffer);

if( getReadBufferApplyMask() )
glReadBuffer(_readBuffer);

#endif
}

_fbo->apply(state);
}
else
fbo_ext->glBindFramebuffer(osg::FrameBufferObject::READ_DRAW_FRAMEBUFFER, static_cast<StateEx *>(&state)->getDefaultFbo());
}

RenderBin::draw(renderInfo,previous);

//...
}




After all this hussle, the most strange part of this solutions is extending osgUtil::CullVisitor. Unfortunately, there is no easy way to inject our RenderStageEx into the rendering pipeline. To solve it, I have overriden the apply(osg::Camera& camera) method. Actually almost all the code come from the original source code but two special care should be given. Firstly, here is what I did:


Code:


void CullVisitorEx::apply(osg::Camera &camera)
{
//...

if(camera.getRenderOrder() == osg::Camera::NESTED_RENDER)
{
handle_cull_callbacks_and_traverse(camera);
}
else
{
osgUtil::RenderStage *prevRenderStage = getCurrentRenderBin()->getStage();
osg::ref_ptr<RenderStageCacheEx> rsCache = dynamic_cast<RenderStageCacheEx *>(camera.getRenderingCache());
if(!rsCache)
{
rsCache = new RenderStageCacheEx();
camera.setRenderingCache(rsCache);
}

osg::ref_ptr<osgUtil::RenderStage> rtts = rsCache->getRenderStage(this);
if(!rtts)
{
rtts = new RenderStageEx();

//...
}
else
rtts->reset();

//...
}

//...
}




I kept only the parts that changed. Instead of new osgUtil::RenderStage(), we should call new RenderStageEx(). Unfortunately, renderstage caching implementation is hidden (implemented inside CPP) so you have to create your own RenderStageCache from scratch. Just copy the source code of it into the beginning of your CullVisitorEx.cpp and rename it.

If you can manage to properly inject all these classes into your project, you can use any combination of FBO rendering with Qt5 and OSG 3.2.1. I have struggled a lot to make it work in current stable release. Hope this helps.

P.S. I am preparing full source code and post it later.

Thank you!

Happy coding,
Can[/code]

------------------
Read this topic online here:
http://forum.openscenegraph.org/viewtopic.php?p=64403#64403





_______________________________________________
osg-users mailing list
osg-...@lists.openscenegraph.org
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Sebastian Messerschmidt

unread,
Aug 3, 2015, 9:43:09 AM8/3/15
to osg-...@lists.openscenegraph.org
Hi Can,

Have you created a full code example yet?
My problem right now is the lack of keyboard events being passed through
to OSG. Any hints on this?

Cheers
Sebastian

Trajce Nikolov NICK

unread,
Aug 3, 2015, 10:20:38 AM8/3/15
to OpenSceneGraph Users
Hi Sebastian,

I am not using Qt but faced the same problem. So here is a 'hack' for Windows if it helps:

    osgViewer::CompositeViewer::Windows wins;
    viewer->getWindows(wins);

    while (!viewer->done())
    {

#if defined(_WIN32)
        MSG msg;
        if (::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
        {
            ::GetMessage(&msg, NULL, 0, 0);

            if (wins.size())
            {
                osgViewer::GraphicsHandleWin32 *hdl = dynamic_cast<osgViewer::GraphicsHandleWin32*>(wins.at(0));
                if(hdl)
                {
                    WNDPROC fWndProc = (WNDPROC)::GetWindowLongPtr(hdl->getHWND(), GWLP_WNDPROC);
                    if (fWndProc && hdl->getHWND())
{
::CallWindowProc(fWndProc,hdl->getHWND(),msg.message, msg.wParam, msg.lParam);
}
                }
            }
        }
#endif
--
trajce nikolov nick

Can Olcek

unread,
Aug 3, 2015, 10:57:20 AM8/3/15
to osg-...@lists.openscenegraph.org
Hi Sebastian,

I have almost completed the example. My original implementation is a little bit complex than this. Thanks to the couple of private replies and discussion, I will post it tomorrow.

But for keyboard inputs, I'm using an event filter.

Something like this:


Code:


class QInputFilter : public QObject
{
Q_OBJECT

protected:
bool eventFilter(QObject *obj, QEvent *event);

void onKeyPress(QKeyEvent *e);
void onKeyRelease(QKeyEvent *e);
};

bool QInputFilter::eventFilter(QObject *obj, QEvent *event)
{
switch(event->type())
{
case QEvent::KeyPress:
onKeyPress(static_cast<QKeyEvent *>(event));
break;
case QEvent::KeyRelease:
onKeyRelease(static_cast<QKeyEvent *>(event));
break;
}

return QObject::eventFilter(obj, event);
}

void QInputFilter::onKeyPress(QKeyEvent *e)
{
if(e->isAutoRepeat())
{
e->ignore();
return;
}

unsigned int key = e->key();
// add pressed keys and add changed keys for current frame
// renderwidget will clear changed keys at the end of frame
Input::PrivateAccess::pressedKeys().insert(key);
Input::PrivateAccess::changedKeys().insert(key);

e->accept();
}

void QInputFilter::onKeyPress(QKeyEvent *e)
{
if(e->isAutoRepeat())
{
e->ignore();
return;
}

unsigned int key = e->key();
// remove released keys and add changed keys for current frame
// renderwidget will clear changed keys at the end of frame
Input::PrivateAccess::pressedKeys().erase(e->key());
Input::PrivateAccess::changedKeys().insert(e->key());

e->accept();
}





Add input listener to your Qt app:


Code:


sdt::Author::QInputFilter inputFilter;
app.installEventFilter(&inputFilter);





I have almost fully static Input class to access keys and mouse states during each frame (paintGL()) I've actually tried to implement Unity3D like approach so inside cull or update traversal I can use Input::getButton(), Input::getKey(), Input::isKeyUp(), etc. methods.

I can add full implemention of this to my full example.

Cheers,
Can

------------------
Read this topic online here:
http://forum.openscenegraph.org/viewtopic.php?p=64591#64591

Sebastian Messerschmidt

unread,
Aug 3, 2015, 11:01:27 AM8/3/15
to osg-...@lists.openscenegraph.org
Am 03.08.2015 um 16:57 schrieb Can Olcek:
> Hi Sebastian,
>
> I have almost completed the example. My original implementation is a little bit complex than this. Thanks to the couple of private replies and discussion, I will post it tomorrow.
>
> But for keyboard inputs, I'm using an event filter.

Okay, I know how to use EventFilters etc.
The point is, that the qt4 implementation somehow passed the events to
OSG, while the qt5 seems to fail to pass any keys.

Cheers
Sebastian

Pierre-Jean Petitprez

unread,
Aug 3, 2015, 11:25:14 AM8/3/15
to osg-...@lists.openscenegraph.org
Hi Sebastian,

I use Qt 5 and OSG 3.2.1 with no problem passing the key events from Qt
to OSG.

In your Qt widget subclass you'll have a method which gets the key events:
void keyPressEvent(QKeyEvent* e)
{
const char *keyData = e->text().toLatin1().data();
gw->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KeySymbol(*keyData));
}
provided that gw is your osgViewer::GraphicsWindow.

Note that for the meta keys (like ALT, CTRL, SHIFT), you'll have to
check something like:
switch(e->key())
{
case Qt::Key_Shift:
key = osgGA::GUIEventAdapter::KEY_Shift_L; //left key is chosen
in an arbitrary way since Qt doesn't differ left or right
break;
case Qt::Key_Control :
key = osgGA::GUIEventAdapter::KEY_Control_L;
break;
case Qt::Key_Alt :
key = osgGA::GUIEventAdapter::KEY_Alt_L;
break;
}
since there is no "value" in e->text().

And the same can be achieved for the keyReleaseEvent,
mousePress/ReleaseEvent, etc. by creating a new GUIEventAdapter from the
Qt event and then send it to the event queue.

Hope that helps.

Cheers
Pierre-Jean

Sebastian Messerschmidt

unread,
Aug 3, 2015, 11:31:55 AM8/3/15
to OpenSceneGraph Users
Hi Pierre,
Okay, I have implemented this almost like this.
The only thing I don't understand is why it was working out of the box
(having a qwidget holding the osg::Viewer::GraphicsWindow) without
manually pushing the events.

Thanks for documenting ;-)

Cheers
Sebastian

John Vidar Larring

unread,
Aug 18, 2015, 7:32:11 AM8/18/15
to OpenSceneGraph Users
Hi Can,

Have you posted / published you full example yet? If so could you reply with a link? Thanks in advance.

Best regards,
John

--
This email was Anti Virus checked by Astaro Security Gateway. http://www.sophos.com


--
This email was Anti Virus checked by Astaro Security Gateway. http://www.sophos.com



--
 


John Vidar Larring Senior Developer

ChyronHego Norge AS Sandakerveien 114a, 0484 Oslo, Norway
Office. +47 2279 7030 - Mobile.+47 4889 9795www.chyronhego.com
Reply all
Reply to author
Forward
0 new messages