Using Dear ImGui from C
These notes cover how to add Dear ImGui into an existing C project using the SDL3_Renderer backend. Since Dear ImGui is C++, we use the C bindings provided by Dear Bindings. The end result is a C application with a couple of example GUI windows.
Bindings §
Since Dear ImGui is written in C++, we need some bindings to enable us to use it from C.
Two popular options are:
Both of these take the Dear ImGui C++ source as input, and perform some processing to output C bindings.
I chose to use Dear Bindings because it is written in Python, which I am very familiar with, whereas cimgui is written in Lua, which I have not used.
Renderer §
Dear ImGui is compatible with many different renderers or backends. I am using SDL_Renderer as my renderer, which is part of SDL3, therefore I will use the sdlrenderer3 backend.
SDL_Renderer is quite basic, but good enough for my use case. It can draw sprites with transparency, rotation and blending. It works on Windows, Linux and Emscripten.
For anything beyond this, it is advisable to use a more advanced renderer, e.g. DirectX, OpenGL, Vulkan.
As explained in the Dear ImGui backend for SDL_Renderer:
// Note that SDL_Renderer is an _optional_ component of SDL3, which IMHO is now largely obsolete.
// For a multi-platform app consider using other technologies:
// - SDL3+SDL_GPU: SDL_GPU is SDL3 new graphics abstraction API.
// - SDL3+DirectX, SDL3+OpenGL, SDL3+Vulkan: combine SDL with dedicated renderers.
// If your application wants to render any non trivial amount of graphics other than UI,
// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user
// and it might be difficult to step out of those boundaries.
Structure §
My C project is built using CMake. It follows a hierarchical structure with a CMakeLists.txt at each level, separate “modules” for distinct pieces of functionality with separate include and src directories:
root/
├── CMakeLists.txt # The "main" CMakeLists.txt
├── app/
│ └── CMakeLists.txt # The "app" CMakeLists.txt
├── include/
│ ├── module_a/
│ │ └── file.h
│ ├── module_b/
│ │ └── file.h
│ └── module_c/
│ └── file.h
└── src/
├── CMakeLists.txt # The "modules" CMakeLists.txt
├── module_a/
│ └── file.c
├── module_b/
│ └── file.c
└── module_c/
└── file.cBoth Dear ImGui and Dear Bindings are provided as a bundle of *.h and *.cpp files in the same directory, i.e. there is no separation of include and src. To address this, I plan to just place all these files within their respective include directories as follows:
root/
├── CMakeLists.txt # The "main" CMakeLists.txt
├── app/
│ └── CMakeLists.txt # The "app" CMakeLists.txt
├── include/
│ ├── module_a/
│ │ └── file.h
│ ├── module_b/
│ │ └── file.h
│ ├── module_c/
│ │ └── file.h
+ │ ├── imgui/ # <-- NEW: Dear ImGui
+ │ │ ├── third_party/
+ │ │ │ └── <Dear ImGui files>
+ │ │ └── my_imconfig.h
+ │ └── dcimgui/ # <-- NEW: Dear Bindings
+ │ └── third_party/
+ │ └── <Dear Bindings files>
└── src/
├── CMakeLists.txt # The "modules" CMakeLists.txt
├── module_a/
│ ├── file.c
│ └── CMakeLists.txt
├── module_b/
│ ├── file.c
│ └── CMakeLists.txt
├── module_c/
│ ├── file.c
│ └── CMakeLists.txt
+ ├── imgui/ # <-- NEW: Dear ImGui
+ │ └── N/A - Handled by dcimgui
+ └── dcimgui/ # <-- NEW: Dear Bindings
+ └── CMakeLists.txt # The "dcimgui" CMakeLists.txt
I like to ensure all third-party code (within my include/src tree) is inside a third_party directory. This is so that:
- It is obvious the code is third-party.
- It is easy to omit third-party code when searching.
- It is easy to omit third-party code when applying code formatting.
Add source §
Add Dear ImGui source §
Go to Dear ImGui Releases and download your preferred release.
will use Dear ImGui v1.92.7.
Download the archive Source code (zip), which contains:
imgui-1.92.7/
├── .editorconfig # IGNORE
├── .gitattributes # IGNORE
├── .gitignore # IGNORE
├── imconfig.h
├── imgui.cpp
├── imgui.h
├── imgui_demo.cpp
├── imgui_draw.cpp
├── imgui_internal.h
├── imgui_tables.cpp
├── imgui_widgets.cpp
├── imstb_rectpack.h
├── imstb_textedit.h
├── imstb_truetype.h
├── LICENSE.txt
├── .github
├── backends/
│ ├── imgui_impl_sdl3.cpp
│ ├── imgui_impl_sdl3.h
│ ├── imgui_impl_sdlrenderer3.cpp
│ ├── imgui_impl_sdlrenderer3.h
│ └── <other files> # IGNORE
├── docs/ # IGNORE
├── examples/ # IGNORE
└── misc/ # IGNORECopy all the above files, except those marked IGNORE, into <Dear ImGui files>.
Note that backends/ contains many implementations to support various backends. I am using SDL_Renderer with SDL3, which requires the sdlrenderer3 and sdl3 backends.
Add Dear Bindings source §
Go to Dear Bindings Releases and download the corresponding release. This must be compatible with the chosen Dear ImGui release.
I will use Dear Bindings v0.19, which is compatible with Dear ImGui v1.92.7.
Download the archive DearBindings_v0.19_ImGui_v1.92.7.zip, which contains:
DearBindings_v0.19_ImGui_v1.92.7/
├── backends/
│ ├── dcimgui_impl_sdl3.cpp
│ ├── dcimgui_impl_sdl3.h
│ ├── dcimgui_impl_sdl3.json
│ ├── dcimgui_impl_sdl3_imconfig.json
│ ├── dcimgui_impl_sdl3_imgui.json
│ ├── dcimgui_impl_sdlrenderer3.cpp
│ ├── dcimgui_impl_sdlrenderer3.h
│ ├── dcimgui_impl_sdlrenderer3.json
│ ├── dcimgui_impl_sdlrenderer3_imconfig.json
│ ├── dcimgui_impl_sdlrenderer3_imgui.json
│ └── <other files> # IGNORE
├── dcimgui.cpp
├── dcimgui.h
├── dcimgui.json
├── dcimgui_internal.cpp
├── dcimgui_internal.h
├── dcimgui_internal.json
├── dcimgui_nodefaultargfunctions.cpp
├── dcimgui_nodefaultargfunctions.h
├── dcimgui_nodefaultargfunctions.json
├── dcimgui_nodefaultargfunctions_internal.cpp
├── dcimgui_nodefaultargfunctions_internal.h
└── dcimgui_nodefaultargfunctions_internal.jsonCopy all the above files, except those marked IGNORE, into <Dear Bindings files>.
As with Dear ImGui, backends/ contains many implementations to support various backends. I am using SDL_Renderer with SDL3, which requires the sdlrenderer3 and sdl3 backends.
Tweak some includes §
Create my_imconfig.h §
Move the file include/imgui/third_party/imconfig.h to include/imgui/my_imconfig.h. This is where Dear ImGui config will live.
Dear ImGui does provide a IMGUI_USER_CONFIG macro that you can use to specify your own config, however this is used in addition to an assumed existing imconfig.h file, i.e.:
#ifdef IMGUI_USER_CONFIG
#include IMGUI_USER_CONFIG
#endif
#include "imconfig.h"So either way, an imconfig.h file is required.
You could avoid moving the file, and instead add imgui/third_party/ to the list of includes:
target_include_directories(
my_app PUBLIC
"${MyProject_SOURCE_DIR}/include"
+ "${MyProject_SOURCE_DIR}/include/imgui/third_party/"
)
However, I prefered to keep my includes simpler, and instead update all occurences of #include "imconfig.h" to #include "imgui/my_imconfig.h instead. This impacts just three files:
include/dcimgui/third_party/dcimgui.hinclude/dcimgui/third_party/dcimgui_nodefaultargfunctions.hinclude/imgui/third_party/imgui.h
Update includes §
Find and replace #include "imconfig.h" with #include "imgui/my_imconfig.h"
Find and replace #include "dcimgui.h" with #include "dcimgui/third_party/dcimgui.h"
As with imconfig.h, you could add dcimgui/third_party/ to the list of includes, to enable dcimgui.h to be found:
target_include_directories(
my_module_a PUBLIC
"${MyProject_SOURCE_DIR}/include"
+ "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/")
However, this would have to be done for all modules that use Dear Bindings, e.g. the CMakeLists.txt for my_module_a, my_module_b, my_module_c etc.
I opted to instead update all occurences of #include "dcimgui.h" to #include "dcimgui/third_party/dcimgui.h". This impacts just four files:
include\dcimgui\third_party\dcimgui.cppinclude\dcimgui\third_party\dcimgui_internal.cppinclude\dcimgui\third_party\backends\dcimgui_impl_sdl3.hinclude\dcimgui\third_party\backends\dcimgui_impl_sdlrenderer3.h
Build Dear ImGui and Dear Bindings §
Add “dcimgui” CMakeLists.txt §
This is where the most interesting part of the build occurs.
Create the file src/dcimgui/CMakeLists.txt. This will build both Dear ImGui and Dear Bindings. Populate it with:
# Dear ImGui
## Headers
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/third_party/backends/imgui_impl_sdlrenderer3.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/third_party/backends/imgui_impl_sdl3.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/third_party/imconfig.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui_internal.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui_demo.cpp")
## Sources
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/imgui/third_party/backends/imgui_impl_sdlrenderer3.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/imgui/third_party/backends/imgui_impl_sdl3.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui_draw.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui_tables.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/imgui/third_party/imgui_widgets.cpp")
# Dear Bindings
## Headers
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/backends/dcimgui_impl_sdlrenderer3.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/backends/dcimgui_impl_sdl3.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/dcimgui.h")
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/dcimgui_internal.h")
## Sources
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/backends/dcimgui_impl_sdlrenderer3.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/backends/dcimgui_impl_sdl3.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/dcimgui.cpp")
list(APPEND LIST_SRC "${MyProject_SOURCE_DIR}/include/dcimgui/third_party/dcimgui_internal.cpp")
# My Config
## Headers
list(APPEND LIST_HDR "${MyProject_SOURCE_DIR}/include/imgui/my_imconfig.h")
add_library(my_dcimgui STATIC
${LIST_SRC}
${LIST_HDR}
)
target_include_directories(
my_dcimgui PUBLIC
"${MyProject_SOURCE_DIR}/include/"
"${MyProject_SOURCE_DIR}/include/imgui/third_party/" # Required for imgui.h to be found
"${MyProject_SOURCE_DIR}/include/imgui/third_party/backends/" # Required for imgui_impl_sdlrenderer3.h and imgui_impl_sdl3.h to be found
)
# Link my_dcimgui to SDL3
if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
# Nothing to do - Emscripten automatically links
else()
message("Linking Dear ImGui against SDL3")
target_link_libraries(my_dcimgui PRIVATE SDL3::SDL3)
target_link_libraries(my_dcimgui PRIVATE SDL3_image::SDL3_image)
target_link_libraries(my_dcimgui PRIVATE SDL3_ttf::SDL3_ttf)
endif()The rest of the CMake updates are just plumbing.
Update “modules” CMakeList.txt §
Include the directory that contains the “dcimgui” CMakeLists.txt added above:
add_subdirectory(dcimgui)Update “app” CMakeLists.txt §
Add the my_dcimgui target to our MY_LIBS list:
list(APPEND MY_LIBS my_dcimgui)Update “main” CMakeLists.txt §
Add CXX to LANGUAGES to add support for C++:
project(
MyProject
VERSION 0.23.42
DESCRIPTION "My Project"
LANGUAGES C CXX)Update .gitignore §
Dear ImGui generates a file called imgui.ini upon running. This stores the state of the windows. This should not be comitted:
imgui.iniExample Usage of Dear Bindings §
This excellent C++ example shows using Dear ImGui with SDL3_Renderer. In the following steps, we translate this example into C using Dear Bindings.
Translating Dear ImGui to Dear Bindings §
The Dear Bindings API is very predictable. As a rule of thumb:
- All top-level
ImGui::prefixes becomeImGui_ - All backend
ImGui_Implprefixes becomecImGui_Impl - All arguments are explicit.
- All
blah.Function(...)calls becomeImGuiBlah_Function(blah, ...)
To give some corresponding examples:
| Dear ImGui (C++) | Dear Bindings (C) |
|---|---|
ImGui::GetStyle(); | ImGui_GetStyle(); |
ImGui_ImplSDLRenderer3_NewFrame(); | cImGui_ImplSDLRenderer3_NewFrame(); |
ImGui::CreateContext(); | ImGui_CreateContext(NULL); |
style.ScaleAllSizes(1.0f); | ImGuiStyle_ScaleAllSizes(style, 1.0f); |
Includes §
Include Dear Bindings:
#include "dcimgui/third_party/backends/dcimgui_impl_sdl3.h"
#include "dcimgui/third_party/backends/dcimgui_impl_sdlrenderer3.h"
#include "dcimgui/third_party/dcimgui.h"Setup §
For the setup:
// Setup Dear ImGui context
CIMGUI_CHECKVERSION();
ImGui_CreateContext(NULL);
ImGuiIO *imguiIo = ImGui_GetIO();
imguiIo->ConfigFlags |=
ImGuiConfigFlags_NavEnableKeyboard; // // Enable Keyboard Controls
// Setup Dear ImGui style
ImGui_StyleColorsDark(NULL);
// Setup scaling
ImGuiStyle *style = ImGui_GetStyle();
style->FontScaleDpi = 1.0;
ImGuiStyle_ScaleAllSizes(style, 1.0);
// Setup Platform/Renderer backends
// NOTE: window and renderer are from SDL3
cImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
cImGui_ImplSDLRenderer3_Init(renderer);Main loop §
Dear ImGui provides a demo window, which is worth using both to verify that everything is set up correctly, and as a useful reference for Dear ImGui functionality.
First, add a flag for whether the demo window is shown. This can be toggled by the user through the GUI:
static bool showDemoWindow = true;Next, extend the main loop with:
// Event handling
int poll = SDL_PollEvent(&sdlEvent);
cImGui_ImplSDL3_ProcessEvent(&sdlEvent);
// Frame setup
cImGui_ImplSDLRenderer3_NewFrame();
cImGui_ImplSDL3_NewFrame();
ImGui_NewFrame();
// Demo
if (showDemoWindow) {
ImGui_ShowDemoWindow(&showDemoWindow);
}
// Example custom window
ImGui_Begin("My Window", NULL, 0);
ImGui_Text("Hello");
ImGui_End();
// <game rendering>
// ImGui rendering
ImGui_Render();
cImGui_ImplSDLRenderer3_RenderDrawData(
ImGui_GetDrawData(), renderer);
SDL_RenderPresent(renderer);Clean up §
When the main loop exits:
// Dear ImGui cleanup
cImGui_ImplSDLRenderer3_Shutdown();
cImGui_ImplSDL3_Shutdown();
ImGui_DestroyContext(NULL);End result §
Now you can build and run your C application, and see two Dear ImGui windows. One is the demo window, another is the “Hello” window:
Go forth and have fun!
Updating §
If you ever want to upgrade the versions of Dear ImGui and Dear Bindings, or change which backends are in use, the steps are:
- Follow Add Dear ImGui source, but replace the previous Dear ImGui source.
- Follow Add Dear Bindings source, but replace the previous Dear Bindings source.
- Follow Update includes to modify the Dear ImGui and Dear Bindings source as described.
