[osg-users] OSG 3.2.1 and Qt5 Widget integration

Can Olcek can.olcek at gmail.com
Wed Jul 22 02:07:32 PDT 2015


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








More information about the osg-users mailing list