How to add Dear Imgui to an SDL2+OpenGL project

30.12.19

During the development of many (visual) applications and tools a simple ad-hoc GUI-library can be very useful to interactively experiment with different settings and inspect values for debugging. The Dear Imgui C++-library fits this niche perfectly. With its immediate-mode paradigm it can be easily integrated into existing applications and adapted to different purposes.

This post is a follow-up to a previous article about configuring an SDL2+OpenGL project. Here we extend the setup to add the Imgui library. Then we correctly integrate it into an application's main loop.

Configure

First we have to make the source code of the library available to our project somehow. Because neither Imgui is packaged in Linux distributions nor C++ has managed to establish a standard language-specific package manager (yet), the easiest solution is to clone the Imgui repository into the project. The we add this to the CMake file:

# ... below the find_package commands for SDL2 and OpengGL

add_library(imgui
    # Main Imgui files
    ../imgui/imgui.cpp ../imgui/imgui_draw.cpp ../imgui/imgui_widgets.cpp
    # SDL2+OpenGL-specific files
    ../imgui/examples/imgui_impl_sdl.cpp ../imgui/examples/imgui_impl_opengl3.cpp)

where ../imgui/ is the correct relative path from the CMake file to the Imgui clone. This adds the imgui target to CMake representing the library. Now we have to add additional information to it.

# Make SDL2 available to Imgui
target_include_directories(imgui PUBLIC ${SDL2_INCLUDE_DIRS})
# imgui/exapmles/ contains the sdl implementation
target_include_directories(imgui PUBLIC ../imgui/ ../imgui/examples/)
# Configure Imgui to use OpengGL through SDL2
target_compile_definitions(imgui PUBLIC IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL_opengl.h>  GL_GLEXT_PROTOTYPES=1)

Now we can add it as dependency to our main target prog like this:

target_link_libraries(prog imgui)

Integrate

After following the instructions of the previous step the project should still compile. Imgui has been added to the project. It just doesn't do anything yet. Next we have to add the initialisation and shutdown code into our main function:

#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_opengl3.h"
//...

int main(int argc, char **argv) {
    // SDL and OpenGL setup...

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();

    // Setup Platform/Renderer bindings
    // window is the SDL_Window*
    // contex is the SDL_GLContext
    ImGui_ImplSDL2_InitForOpenGL(window, context);
    ImGui_ImplOpenGL3_Init();

    // Main loop
    bool done = false;
    while(!done) {
        //...
    }

    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplSDL2_Shutdown();
    ImGui::DestroyContext();

    // Other cleanup...

    return 0
}

Inside the main loop we have to forward all SDL events to Imgui so they can be handled correctly and the GUI can respond accordingly. Before the actual main loop logic we have to add the Imgui frame initialisation code. And at the end we finalize the frame and render Imugi top of everything else:

// Inside main loop

// Handle events
SDL_Event event;
while (SDL_PollEvent(&event))
{
    // Forward to Imgui
    ImGui_ImplSDL2_ProcessEvent(&event);
    if (event.type == SDL_QUIT
        || (event.type == SDL_WINDOWEVENT
        && event.window.event == SDL_WINDOWEVENT_CLOSE
        && event.window.windowID == SDL_GetWindowID(window))) {
        done = true;
    }
}

// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(window);
ImGui::NewFrame();

// Frame logic here...

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render other stuff...
// Render imgui
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);

Use

Now we get to the fun part! With all the plumbing done, we can use Imgui anywhere in our code - as long as it called every frame, i.e. every iteration of the main loop. For example we might have some simulation code and somewhere deep inside a hierarchy of update methods, none of which is concerned with GUI code, we can make an object interactable through a GUI:

ImGui::Begin("MyWindow");
ImGui::Checkbox("Boolean property", &this->someBoolean);
if(ImGui::Button("Reset Speed")) {
    // This code is executed when the user clicks the button
    this->speed = 0;
}
ImGui::SliderFloat("Speed", &this->speed, 0.0f, 10.0f);
ImGui::End();

This code enables the user to inspect and change a boolean property through a checkbox, and speed through a slider with an additional option to simply reset it with a button. Moreover the slider can be Ctrl-clicked to input arbitrary values!

Such snippets can be added (or later removed) throughout the code, to allow the user to easily interact with different parts of the program. This approach is at the same time simpler, more scalable, and more powerful than a simple hand-written display / interaction code would be. Therefore it is clear that Dear Imgui is a superb tool to greatly streamline the process of creating a (debug) GUI.