Running custom OpenGL alongside V3d viewer

Hey,

I'm currently working on a viewer for 3D models as part of a larger project. I decided to use OpenCASCADE for visualization, however I'm quite new to the ecosystem. I would like to know how I can render custom OpenGL on top of a 3D viewer.

I've managed to build a basic viewer based on OpenGL and GLFW using two subclasses from AIS_ViewController and Aspect_Window. The model is being rendered using V3d_Viewer and I would like to be able to render custom OpenGL on top of rendered output from V3d_Viewer. I found this article and this guide to upgrading which were a big help in getting some ground work done.

Following some instructions from those resources I created two classes: GLViewGUI, the class that is supposed to perform the custom rendering:

class GLViewGUI : public OpenGl_View {
    DEFINE_STANDARD_RTTI_INLINE(GLViewGUI, OpenGl_View)
  public:
    GLViewGUI (const Handle(Graphic3d_StructureManager)& theMgr,
               const Handle(OpenGl_GraphicDriver)& theDriver,
               const Handle(OpenGl_Caps)& theCaps,
               OpenGl_StateCounter* theCounter)
               : OpenGl_View(theMgr, theDriver, theCaps, theCounter) {
        myRenderParams.Method = Graphic3d_RM_RASTERIZATION;
    };

    void render(Graphic3d_Camera::Projection theProjection,
                OpenGl_FrameBuffer*          theReadDrawFbo,
                OpenGl_FrameBuffer*          theOitAccumFbo,
                const Standard_Boolean       theToDrawImmediate) Standard_OVERRIDE {
        OpenGl_View::render(theProjection, theReadDrawFbo, theOitAccumFbo, theToDrawImmediate);
        if (theToDrawImmediate)
            return;

        const Handle(OpenGl_Context)& aCtx = myWorkspace->GetGlContext();
        GLfloat aVerts[] = { -0.5f, -0.5f, 0.0f,
                              0.5f, -0.5f, 0.0f,
                              0.0f,  0.5f, 0.0f,
        };
        aCtx->core20->glEnableClientState(GL_VERTEX_ARRAY);
        aCtx->core20->glVertexPointer(3, GL_FLOAT * 3, GL_FALSE, aVerts);
        aCtx->core20fwd->glDrawArrays(GL_TRIANGLES, 0, 3);
        aCtx->core20->glDisableClientState(GL_VERTEX_ARRAY);
    }
};

And this GLExtensibleDriver class that allows me to register custom OpenGL_View classes:

class GLExtensibleDriver : public OpenGl_GraphicDriver {
    DEFINE_STANDARD_RTTI_INLINE(GLExtensibleDriver, OpenGl_GraphicDriver)
  public:
    explicit GLExtensibleDriver (const Handle(Aspect_DisplayConnection)& theDisp,
                                 const Standard_Boolean                  theToInitialize = Standard_True)
                                 : OpenGl_GraphicDriver(theDisp, theToInitialize) {};

    Handle(Graphic3d_CView) CreateView (const Handle(Graphic3d_StructureManager)& theMgr) Standard_OVERRIDE {
        Handle(OpenGl_View) aView = new OpenGl_View (theMgr, this, myCaps, &myStateCounter);
        myMapOfView.Add (aView);
        for (NCollection_List<Handle(Graphic3d_Layer)>::Iterator aLayerIter (myLayers); aLayerIter.More(); aLayerIter.Next())
        {
            const Handle(Graphic3d_Layer)& aLayer = aLayerIter.Value();
            aView->InsertLayerAfter (aLayer->LayerId(), aLayer->LayerSettings(), Graphic3d_ZLayerId_UNKNOWN);
        }
        return {aView};
    }

    template<class View> requires std::is_base_of_v<OpenGl_View, View>
    Handle(OpenGl_View) CreateView (const Handle(Graphic3d_StructureManager)& theMgr) {
        Handle(View) aView = new View (theMgr, this, myCaps, &myStateCounter);
        myMapOfView.Add (aView);
        for (NCollection_List<Handle(Graphic3d_Layer)>::Iterator aLayerIter (myLayers); aLayerIter.More(); aLayerIter.Next())
        {
            const Handle(Graphic3d_Layer)& aLayer = aLayerIter.Value();
            aView->InsertLayerAfter (aLayer->LayerId(), aLayer->LayerSettings(), Graphic3d_ZLayerId_UNKNOWN);
        }
        return {aView};
    }

    template<class View> requires std::is_base_of_v<OpenGl_View, View>
    Handle(OpenGl_View) CreateView () {
        Handle(Graphic3d_StructureManager) aStructureManager = new Graphic3d_StructureManager(this);
        return CreateView<View>(aStructureManager);
    }
};

These classes are created and used a follows: On creation:

Handle(GLExtensibleDriver) aGraphicDriver = new GLExtensibleDriver ([Window Class]->GetDisplay(), false);
Handle(V3d_Viewer) aViewer = new V3d_Viewer (aGraphicDriver);
[...]
myView = aViewer->CreateView();
myView->SetImmediateUpdate (false);
myView->SetWindow (myOcctWindow, myOcctWindow->NativeGlContext());
[...]
myContext = new AIS_InteractiveContext (aViewer);
aGUIViewer = aGraphicDriver->CreateView<GLViewGUI>();
aGUIViewer->SetWindow (myOcctWindow, myOcctWindow->NativeGlContext());

On rendering:

while (!glfwWindowShouldClose (myOcctWindow->getGlfwWindow())) {
    glfwWaitEvents();
    if (!myView.IsNull()) {
        glfwPollEvents();
        aGUIViewer->Redraw();
        FlushViewEvents (myContext, myView, true);
    }
}

This whole set-up doesn't work at all however. The first and most pressing issue is that calling aGUIViewer->Redraw() clears the OpenGL back buffer and flips it after drawing. Of course Id like to prevent the redraw from clearing or flipping the buffer. Looking at the source I don't see any easy way to circumvent that.

The second issue is that nothing is being drawn. The OpenGL code that is run inside GLViewGUI::render() gets translated into one glDrawArray call as expected but draws completely arbitrary vertices using textures that have not been bound. I'm generally not very satisfied with the OpenGl_Context.coreXX API. Some functions are missing and some are defined in a weird way I am unable to understand. This might just be a lack of documentation.

Id much appreciate your support!

gkv311 n's picture

I'm generally not very satisfied with the OpenGl_Context.coreXX API. Some functions are missing and some are defined in a weird way

This API is just a (structured) table of C functions defined by OpenGL itself, and their documentation could be found on Khronos resources. If you specify which exactly functions are missing it would be simpler to answer.

Of course Id like to prevent the redraw from clearing or flipping the buffer.

It is unclear from context why you would want to prevent clearing and flipping the buffer. You may disable flipping, though, by changing flag OpenGl_Caps::buffersNoSwap.

        aGUIViewer->Redraw();
        FlushViewEvents (myContext, myView, true);

AIS_ViewController::FlushViewEvents() calls V3d_Viewer::Redraw() internally. so that these two lines cause redrawing and flipping window contents twice.

        aCtx->core20->glEnableClientState(GL_VERTEX_ARRAY);
        aCtx->core20->glVertexPointer(3, GL_FLOAT * 3, GL_FALSE, aVerts);
        aCtx->core20fwd->glDrawArrays(GL_TRIANGLES, 0, 3);
        aCtx->core20->glDisableClientState(GL_VERTEX_ARRAY);

Your OpenGL rendering code is incomplete - there is no GLSL program setup, no OpenGL pipeline state setup, so that rendering results would be undefined.
Consider taking a look at VUserDrawObj defined in src/OpenGlTest/OpenGlTest_Commands.cxx as a hello-world sample drawing something using OpenGL context.
It follows another approach for embedding OpenGL routines, though, by defining custom AIS object, but rendering code could look similar.

Nicholas Karwath's picture

Thanks for the quick and helpful answer @gkv311 n!

I implemented some of the changes you proposed. I was able to get something to render after looking at src/OpenGlTest/OpenGlTest_Commands.cxx and it proved to be a good resource.

The OpenGL code looks a little like this now:

const Handle(OpenGl_Context)& aCtx = myWorkspace->GetGlContext();
const OpenGl_Aspects* aMA = myWorkspace->Aspects();
aMA->Aspect()->MarkerType();
OpenGl_Vec4 aColor{1.0f, 1.0f, 1.0f, 1.0f};

aCtx->ShaderManager()->BindLineProgram (Handle(OpenGl_TextureSet)(), Aspect_TOL_SOLID,
                                        Graphic3d_TOSM_UNLIT, Graphic3d_AlphaMode_Opaque, false,
                                        Handle(OpenGl_ShaderProgram)());
aCtx->SetColor4fv (aColor);

GLfloat aVerts[] = { -50.,   0., 0.,
                      50.,   0., 0.,
                       0., -50., 0.,
                       0.,  50., 0.,
};
Handle(OpenGl_VertexBuffer) aVertBuffer = new OpenGl_VertexBuffer();
aVertBuffer->Init (aCtx, 3, 4, aVerts);
aVertBuffer->BindAttribute  (aCtx, Graphic3d_TOA_POS);
aCtx->core11fwd->glDrawArrays (GL_LINE_LOOP, 0, 4);
aVertBuffer->UnbindAttribute(aCtx, Graphic3d_TOA_POS);
aVertBuffer->Release (aCtx.get());

This is basically just a modified version of the render function from OpenGlTest_Commands.cxx. One problem I faced is that the coordinate system seems to be based on screen space / projection space but I assume that just has to do with the shader used (I've attached a screenshot). Another problem I faced was with the draw mode GL_LINE. It doesn't seem to work correctly. For that reason I am using GL_LINE_LOOP here. How persistent are the objects here? Can OpenGl_VertexBuffer be reused outside of the current draw?

Of course Id like to prevent the redraw from clearing or flipping the buffer.

It is unclear from context why you would want to prevent clearing and flipping the buffer. You may disable flipping, though, by changing flag OpenGl_Caps::buffersNoSwap.

aGUIViewer->Redraw();
FlushViewEvents (myContext, myView, true);

AIS_ViewController::FlushViewEvents() calls V3d_Viewer::Redraw() internally. so that these two lines cause redrawing and flipping window contents twice.

Id like to only clear and swap the buffer on one of the ::Redraw() calls as to only flip it once. I only want to clear the buffer once since I want to draw over the 3D scene with the custom GL. To prevent flipping I tired doing this: myWorkspace->GetGlContext()->caps->buffersNoSwap = true;, which just caused the application to sigfault.

Lastly, I would like to know the process of using custom shaders, bindings, buffers and descriptors. Seeing how complex this API is I fear I am unable to run commands close to hardware.

Thanks for you help!