Using OpenCascade with Dear Imgui and GLFW

I am expanding a GLFW example with some code to work with Dear ImGui.
My main subroutine looks like this:

void GlfwOcctView::run()
{
  initWindow (800, 600, "glfw occt");
  initViewer();
  initDemoScene();
  if (myView.IsNull())
  {
    return;
  }

  myView->MustBeResized();
  myOcctWindow->Map();
  initUI();
  mainloop();
  cleanupUI();
  cleanup();
}

cleanupUI() was taken from Dear ImGui repo
Here are ImGUI-related routines:

void GlfwOcctView::initUI()
{
    // Setup Dear ImGui context
    const char* glsl_version = "#version 330";
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();    
    // Setup Platform/Renderer bindings
    ImGui_ImplGlfw_InitForOpenGL(myOcctWindow->getGlfwWindow(), true);
    ImGui_ImplOpenGL3_Init(glsl_version);
    // Setup Dear ImGui style
    ImGui::StyleColorsDark();
}

void GlfwOcctView::processUI()
{
    // feed inputs to dear imgui, start new frame 
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();
    
    ImGui::Begin("STEP files");   
    
    ImGui::Button("Add File");
    ImGui::SameLine();
    ImGui::Button("Clear List");
    //ImGui::ListBox("Files",&currentItem,listboxItems,currentItemsCount,10);
    
    ImGui::End();

    // Rendering
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

void GlfwOcctView::cleanupUI()
{
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
}

GlfwOcctView::processUI() is called from mainloop(). Now here is the problem:
I define mainloop() like this:

void GlfwOcctView::mainloop()
{
  while (!glfwWindowShouldClose (myOcctWindow->getGlfwWindow()))
  {
    // glfwPollEvents() for continuous rendering (immediate return if there are no new events)
    // and glfwWaitEvents() for rendering on demand (something actually happened in the viewer)
    //glfwPollEvents();
    glfwWaitEvents();
    if (!myView.IsNull())
    {
        processUI();
      
        glfwSwapBuffers(myOcctWindow->getGlfwWindow());
        //FlushViewEvents(myContext, myView, true);
    }
    
  }
}

I mean, if I use
glfwSwapBuffers(myOcctWindow->getGlfwWindow());
, then my scene becomes unresponsible, but my Dear Imgui window shows and can be controlled
If I use
FlushViewEvents(myContext, myView, true);
then my Dear Imgui window is not showing at all, but scene is responsible.
If I have both these lines, glfwSwapBuffers and FlushViewEvents then my Dear ImGui window and scene shows, but with graphical artifacts - flickering heavily, Dear ImGui window not redrawing properly when it covers a scene.

So, the question is: How do I use Dear Imgui with Opencascade, in glfw window? I'd like to base my code on an example, since it has good design

gkv311 n's picture

Let's start from architectural point of view:

  • GLFW takes responsibility to initialize OpenGL-capable native window and translates input events.
  • OCCT implements a graphical driver on top of OpenGL or OpenGL ES.
    • In GLFW+OCCT combination, GLFW creates a native window (GLFWwindow), brings it to OCCT (Aspect_Window) to initialize OpenGL renderer (OpenGl_GraphicDriver) and redirects input events to manage 3D view (AIS_ViewController / Aspect_WindowInputListener)
  • ImGUI is a library to draw GUI on top of various renderers (OpenGL, Direct3D, etc.) and to handle input events from various systems (like GLFW).
    • Rendering layer is abstracted in ImGUI with different implementations provided out of the box like OpenGL3 (ImGui_ImplOpenGL3_Init).
    • Window input events is also abstracted in ImGUI with different implementations available like GLFW (ImGui_ImplGlfw_InitForOpenGL).

Here you may notice that as OCCT takes responsibility to wrap graphic driver, logically it is expected that somebody should write ImGUI renderer plugin on top of OCCT abstraction layer (e.g. creating Graphic3d primitive arrays, AIS objects and so on instead of using OpenGL directly). In this way, one could use such plugin for ImGUI naturally on different platforms supported by OCCT (and potentially using new Graphic3d_GraphicDriver implementations using other graphic APIs). The same might be considered for wrapping window events through OCCT as well (e.g. implementing some ImGUI driver for AIS_ViewController / Aspect_WindowInputListener instead of ImGui_ImplGlfw_InitForOpenGL).

That is what one would expect taking a look at ImGUI and OCCT design... But from the other point of view, that would require a plenty of work (probably several weeks). To avoid this, we may consider more limited alternative approaches - like using existing OpenGL3 renderer implementation coming with ImGUI. For that, however, one should understand basics how to embed another OpenGL into OCCT renderer. There are two basic approaches:

  • Subclassing OpenGl_Element and drawing ImGUI content within a custom AIS_InteractiveObject like it is demonstrated by VUserDrawObj in src/OpenGlTest/OpenGlTest_Commands.cxx. You might probably want to set Graphic3d_ZLayerId_TopOSD to this AIS_InteractiveObject to make this GUI drawn on top of 3D scene, and probably do some tricks like Graphic3d_TMF_2d and simulating object boundaries to fill in entire window dimensions.
  • Subclassing OpenGl_View and overriding some method like renderScene() or another more suitable place for putting ImGUI rendering code inside OpenGL renderer.

In both cases you might need to carefully prepare / restore global OpenGL state before / after ImGUI OpenGL renderer to not corrupt OCCT rendering / ensure ImGUI rendering.

Less intrusive approach could be achieved by hacks like OpenGl_Caps::buffersNoSwap=true so that you could call glfwSwapBuffers() manually after FlushViewEvents() (or probably better - within overridden AIS_ViewController::handleViewRedraw()) and before ImGUI rendering code.

Ivan P's picture

Thanks a lot, it worked.
In initViewer() I added this call:

aGraphicDriver->SetBuffersNoSwap(true);

And then I changed mainloop() as advised:

void GlfwOcctView::mainloop()
{
  while (!glfwWindowShouldClose (myOcctWindow->getGlfwWindow()))
  {
    // glfwPollEvents() for continuous rendering (immediate return if there are no new events)
    // and glfwWaitEvents() for rendering on demand (something actually happened in the viewer)
    //glfwPollEvents();
    glfwWaitEvents();
    if (!myView.IsNull())
    {               
        
        FlushViewEvents(myContext, myView, true);
        processUI();
        glfwSwapBuffers(myOcctWindow->getGlfwWindow());
    }
    
  }
}

There is a little problem though: when moving ImGui window, my scene reacts too on moves. It's worth investigating too

Attachments: 
Ivan P's picture

This link may be useful:
https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-how-can-i-tell-whether-to-dispatch-mousekeyboard-to-dear-imgui-or-my-application
It is handled by Dear ImGui

I changed event handlers in GlfwOcctView.h

//! Mouse scroll callback.
  static void onMouseScrollCallback (GLFWwindow* theWin, double theOffsetX, double theOffsetY)
  { 
	  ImGuiIO& io = ImGui::GetIO();
	  if (!io.WantCaptureMouse) {
	  toView(theWin)->onMouseScroll (theOffsetX, theOffsetY); 
	  }
  }

  //! Mouse click callback.
  static void onMouseButtonCallback (GLFWwindow* theWin, int theButton, int theAction, int theMods)
  { 
	  ImGuiIO& io = ImGui::GetIO();
	  if (!io.WantCaptureMouse){
	  toView(theWin)->onMouseButton (theButton, theAction, theMods); 
	  }
  }

  //! Mouse move callback.
  static void onMouseMoveCallback (GLFWwindow* theWin, double thePosX, double thePosY)
  { 
	  ImGuiIO& io = ImGui::GetIO();
	  if (!io.WantCaptureMouse) {
      toView(theWin)->onMouseMove ((int )thePosX, (int )thePosY); 
	  }
	  else {
		  toView(theWin)->myView->Redraw();
	  }
  }

Note the statement toView(theWin)->myView->Redraw(); It is needed to manually redraw View if we do something with ImGui window

Hafeez Gbadamosi's picture

I was going to suggest using Imgui::IsItemHovered() to detect if the mouse was interacting with the ui but this is a much better solution

Hafeez Gbadamosi's picture

I noticed that due to the buffer swapping hack, the screen starts flickering after zooming with the mouse scroll. Are you experiencing this in your program too

weiguo liu's picture

Yes, it’s the same on my side. After the rotation, it shakes and the cursor moves just fine. I’m trying to solve it. Did you solve the problem?

weiguo liu's picture

Yes, it’s the same on my side. After the rotation, it shakes and the cursor moves just fine. I’m trying to solve it. Did you solve the problem?

weiguo liu's picture

When you use glfwWaitEvents(), there is no problem.

tsunghung tsai's picture

I notice onece remove viewer gird code, the screen will flickering,
aViewer->ActivateGrid(Aspect_GT_Rectangular, Aspect_GDM_Lines);
Has anyone encountered the same problem?