Hello! Welcome back to our project.
In our last lesson, we successfully initialized an OpenGL context using GLAD. You now have a C++ application that opens a window and clears it to a solid color, giving us a blank canvas. We have the foundational boilerplate, and now it's time to start painting our user interface.
Today, we will integrate the Dear ImGui library with the window and graphics context you've already created. This is a major step toward building the visual frontend for our telemetry engine. By the end of this lesson, you will render your first interactive UI element inside your application window.
What is Dear ImGui?
Dear ImGui (often just called ImGui) is a GUI library designed for creating tools, debuggers, and visualizations in real-time rendering applications. It's exceptionally well-suited for our project for a few key reasons:
- Immediate Mode vs. Retained Mode: Unlike traditional UI toolkits (like Qt or WinForms) that maintain a complex state graph of UI objects (retained mode), ImGui operates on an "immediate mode" philosophy. You describe the UI you want to see for the current frame from scratch, every frame. This stateless approach makes it simple to use, highly flexible, and less prone to complex state management bugs, which is ideal for a dynamic tool like a profiler.
- Performance: It's written in C++ with performance as a priority. It generates simple vertex buffers that are rendered by your application's graphics pipeline (OpenGL in our case), adding very little overhead. This is crucial for our goal of building a "zero-overhead" telemetry system.
- Self-Contained: It has no external dependencies outside of your windowing and graphics backend, which we already have (GLFW and OpenGL).
ImGui is the industry standard for in-game and real-time application developer tools.

Let's get it running in our project.
Step 1: Getting the ImGui Source Code
Like GLAD, we will add the ImGui source files directly to our project. The official documentation recommends this approach over trying to build it as a separate library.
- Go to the Dear ImGui GitHub repository.
- Click on the "releases" tab and download the source code
.zipfor the latest version (e.g., v1.90.8). - Extract the zip file to a known location.
Step 2: Adding ImGui Files to Your Project
Now, we need to copy the relevant files from the extracted folder into our project. We need two sets of files: the core ImGui files and the "backend" implementation files for GLFW and OpenGL 3.
Create a new vendor directory in your project root, if you don't have one. Inside it, create an imgui directory. This is where we'll place the files.
Your project structure should look like this:
YourProject/
โโโ CMakeLists.txt
โโโ main.cpp
โโโ src/
โ โโโ glad.c
โโโ include/
โ โโโ glad/
โ โโโ KHR/
โโโ vendor/ <-- NEW
โโโ imgui/ <-- NEW
Now, copy the following files from the unzipped ImGui folder into your new vendor/imgui directory:
- Core files (from the root of the ImGui folder):
imconfig.himgui.himgui.cppimgui_draw.cppimgui_internal.himgui_tables.cppimgui_widgets.cppimstb_rectpack.himstb_textedit.himstb_truetype.h
- Backend files (from the
backends/directory inside the ImGui folder):imgui_impl_glfw.himgui_impl_glfw.cppimgui_impl_opengl3.himgui_impl_opengl3.cpp
The following video from The Cherno provides a clear walkthrough of this file identification and copying process.
Watch this short clip to see exactly which files to grab from the ImGui source and how to organize them within your project directory. It covers both the core files and the specific backend files we need for GLFW and OpenGL.
Watch from 2:06 to 4:12. Focus on the process of identifying and copying the necessary ImGui files into a vendor folder. You can ignore the parts about deleting the main.cpp from the example.
Step 3: Updating the Build System (CMake)
Next, we need to tell CMake about these new files so they get compiled and included in our final executable.
Open your CMakeLists.txt and make two changes:
- Add the new ImGui
.cppfiles to youradd_executablecommand. - Add the
vendor/imguidirectory to yourtarget_include_directories.
Your CMakeLists.txt should now look like this:
cmake_minimum_required(VERSION 3.15)
project(TelemetryEngine)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Fetch GLFW
include(FetchContent)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.3.8
)
FetchContent_MakeAvailable(glfw)
# Our executable
add_executable(
TelemetryEngine
main.cpp
src/glad.c
# --- Add ImGui source files ---
vendor/imgui/imgui.cpp
vendor/imgui/imgui_draw.cpp
vendor/imgui/imgui_tables.cpp
vendor/imgui/imgui_widgets.cpp
vendor/imgui/imgui_impl_glfw.cpp
vendor/imgui/imgui_impl_opengl3.cpp
)
# Link against GLFW
target_link_libraries(TelemetryEngine PRIVATE glfw)
# Tell our target where to find headers
target_include_directories(
TelemetryEngine
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui # <-- Add this line
)
# Prevent GLFW from including its own GL headers
target_compile_definitions(TelemetryEngine PRIVATE GLFW_INCLUDE_NONE)
After saving this file, regenerate your build files using CMake. Your project is now ready for the C++ integration.
Step 4: Integrating ImGui into Your Code
The process of integrating ImGui follows a standard lifecycle: Initialize, Create a New Frame, Render UI Widgets, Render the Frame, and Shutdown. The official ImGui wiki provides an excellent example for GLFW and OpenGL, which we will follow.
Getting Started ยท ocornut/imgui Wiki
This is the official 'Getting Started' guide from the ImGui wiki. It's the most authoritative source. We will follow the example specifically for GLFW and OpenGL.
Find the section titled 'Example: If you are using GLFW + OpenGL/WebGL'. Skim through the code snippets for 'Includes', 'Initialization', 'start of main loop', 'end of main loop', and 'Shutdown'. This is the exact pattern we will implement.
Let's apply this pattern to your main.cpp.
1. Includes
First, include the necessary ImGui headers at the top of main.cpp, right after your other includes.
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
// --- New ImGui Includes ---
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
2. Initialization
After you've created your GLFW window and initialized GLAD, you need to initialize ImGui. This sets up the ImGui context and configures the backends to work with your window and renderer.
// ... after gladLoadGLLoader ...
// --- Setup Dear ImGui context ---
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// --- Setup Dear ImGui style ---
ImGui::StyleColorsDark();
//ImGui::StyleColorsClassic();
// --- Setup Platform/Renderer backends ---
const char* glsl_version = "#version 330";
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
3. The Render Loop
Your main while loop needs to be updated. The sequence is critical:
- Start a new ImGui frame.
- Define the UI elements you want to draw for this frame (e.g., windows, buttons).
- Render the ImGui draw data.
// Inside the Render Loop
while (!glfwWindowShouldClose(window)) {
// Poll events
glfwPollEvents();
// --- Start the Dear ImGui frame ---
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// --- UI Code Here ---
// Example: Show a simple window
ImGui::Begin("Hello, world!");
ImGui::Text("This is some useful text.");
ImGui::End();
// --- Rendering ---
glClearColor(0.1f, 0.1f, 0.15f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Render ImGui draw data
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// Swap buffers
glfwSwapBuffers(window);
}
Notice how ImGui rendering happens after glClear but before glfwSwapBuffers. ImGui draws its UI on top of whatever you have already rendered.
4. Shutdown
Finally, when your application exits, you need to clean up the ImGui resources. Add these calls just before glfwDestroyWindow.
// --- Cleanup ---
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
The video below walks through this exact sequence of Init, NewFrame, Render, and Shutdown calls.
This clip reinforces the C++ integration steps. It shows where to place the initialization, main loop, and shutdown calls within your existing code.
Watch from 5:33 to 7:38. The presenter adds the ImGui initialization calls after creating the window, then integrates the NewFrame/Render calls into the main loop, and finally adds the shutdown calls.
Complete Code and Verification
Here is the complete main.cpp with all the changes. Compile and run your project.
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
int main() {
// Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}
// Set window hints for OpenGL 3.3 Core Profile
const char* glsl_version = "#version 330";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// Create the window
GLFWwindow* window = glfwCreateWindow(1280, 720, "Telemetry Engine", NULL, NULL);
if (window == nullptr) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
// Initialize GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD" << std::endl;
glfwTerminate();
return -1;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
// The Render Loop
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// --- Our UI code goes here ---
{
ImGui::Begin("Info");
ImGui::Text("Hello from ImGui!");
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
// Rendering
glClearColor(0.1f, 0.1f, 0.15f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
If everything is correct, you should see your window with a small, movable "Info" panel inside it displaying some text and your application's frame rate. Congratulations!
Test your understanding!
Inside the ImGui::Begin("Info"); ... ImGui::End(); block, add a button. If the button is clicked, it should print "Button Pressed!" to the standard console (std::cout).
Hint: Look up ImGui::Button() in the ImGui documentation or examples. It returns true when clicked.
Show answer
// Inside the "Our UI code goes here" block
{
ImGui::Begin("Info");
ImGui::Text("Hello from ImGui!");
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
// Add this code for the button
if (ImGui::Button("Click Me")) {
std::cout << "Button Pressed!" << std::endl;
}
ImGui::End();
}
Conclusion
Fantastic work today. You've taken a significant step by integrating a powerful UI library into our application. This forms the backbone of the entire visualizer suite we aim to build.
Key Takeaways:
- ImGui uses an "immediate mode" paradigm, where the UI is rebuilt from code every frame.
- Integrating ImGui involves adding its core source files plus backend implementations for your specific platform (GLFW) and renderer (OpenGL).
- The ImGui lifecycle within the main loop is strictly ordered:
NewFramecalls -> UI widget code ->Render->RenderDrawData. - Initialization and Shutdown calls must properly wrap the main loop to manage the ImGui context.
What's Next?
Now that ImGui is up and running, our next step is to learn how to use its rich set of features effectively. In the next lesson, we will focus on creating a basic window with simple ImGui widgets, such as text inputs, sliders, and buttons, to start building out the interactive controls for our telemetry engine.