Hello! Welcome to a new module in our journey to build a high-performance telemetry engine.
In the previous module, we focused entirely on the "producer" side of our data pipeline. You learned how to capture events, optimize string storage with an interner, and serialize event data into a compact binary format. We now have an efficient mechanism for preparing performance data on our application threads. The next big question is: how do we see this data?
This brings us to the user-facing side of our project: the visualization suite. Over the next few lessons, we'll build the graphical interface that will display our captured data. We'll be using Dear ImGui, a powerful and popular C++ library for creating immediate mode graphical user interfaces.
Today's lesson is the crucial first step. Before we can draw anything, we need a window and a way to render graphics in it. Our goal is to configure a build system (CMake) to link the external libraries required for windowing (GLFW) and rendering (OpenGL). This is the foundational plumbing for any graphical application in C++.

1. What is a Build System?
So far, you've been compiling single C++ files, likely with a simple command like g++ main.cpp. As projects grow to include multiple files and depend on external code, this manual approach becomes unmanageable. We need a tool to automate this process.
A build system is a tool that automates the process of compiling source code into a runnable executable. CMake is not a build system itself, but a meta-build system. It doesn't compile your code directly. Instead, it reads a configuration file you write (CMakeLists.txt) and generates the project files for a native build system on your specific platform (like Visual Studio projects on Windows or Makefiles on Linux).
This is why CMake is so powerful for cross-platform development: you write one CMakeLists.txt, and it can generate the correct build instructions for almost any compiler and operating system.
CMake Tutorial: Building Your First C++ Project & Beyond
To understand CMake's role, let's read a brief, high-level introduction from the article 'CMake Tutorial: Building Your First C++ Project & Beyond'.
Read the section titled 'What is CMake?'. Focus on the distinction between CMake as a 'meta-build system' and a direct compiler. Understand that you are describing a blueprint for the build.
2. The Core Dependencies for a Graphics Application
To create a graphical application from scratch, we need to solve three main problems:
- Window Management: How do we create an application window, handle resizing, and process mouse and keyboard input? This is highly OS-specific.
- Graphics Rendering: How do we draw pixels, lines, and triangles to the screen? This is handled by a graphics API.
- API Loading: How does our application find the functions for the graphics API, which are provided by the computer's graphics driver?
Instead of solving these complex problems ourselves, we use specialized external libraries. For our project, we will use:
- GLFW: A library that provides a simple, cross-platform API for creating windows, handling user input, and managing OpenGL contexts.
- OpenGL: The industry-standard API (Application Programming Interface) for rendering 2D and 3D graphics. It's not a library you link in the traditional sense, but an interface to the graphics driver on your machine.
- GLAD: An OpenGL Loading Library. Since OpenGL functions are implemented by your graphics driver, their memory addresses aren't known at compile time. GLAD generates the necessary boilerplate code to find and load these function pointers at runtime so we can call them.

3. Managing Dependencies with Modern CMake: FetchContent
Now we know what we need, but how do we tell CMake to find these libraries and link them to our program?
Older tutorials and projects often use a command called find_package(). This command searches the user's computer for a pre-installed version of the library. This can be brittle; it might fail if the library is not installed, is in an unusual location, or is the wrong version. This is the approach you might see in the videos "CMake Project Setup (Windows, OpenGL, GLFW, GLAD)" and "Setting up an OpenGL Project in Ubuntu [VSCode, GLFW, GLAD, CMake]", which rely on manual downloads or system package managers.
A more robust and modern approach is to use CMake's FetchContent module. FetchContent can automatically download the source code for a dependency from a repository (like GitHub), build it if necessary, and make it available to your project.
This has major advantages for your project:
- Self-Contained: Anyone can clone your project and build it without having to manually install dependencies first.
- Version Control: You can specify the exact version of a library, ensuring that everyone uses the same code and avoiding "works on my machine" issues.
- Cross-Platform: It works identically on Windows, macOS, and Linux.
This is the standard for modern, portable C++ projects and is perfect for a portfolio piece you intend to share.
CMake Tutorial: Building Your First C++ Project & Beyond
The 'CMake Tutorial' resource explains the benefits of FetchContent over find_package quite well.
Read the subsection 'Linking to (Open-Source) Third-Party Libraries'. Focus on the explanation of find_package() vs. FetchContent. The example uses a library called Raylib, but the principles are identical for fetching GLFW and GLAD.
4. Writing the CMakeLists.txt
Let's put theory into practice and write our CMakeLists.txt file. We will build it step-by-step.
Step 1: Basic Project Setup
First, every CMakeLists.txt needs some basic information: the minimum CMake version required, the project name, and the C++ standard to use. Create a file named CMakeLists.txt in your project's root directory.
# Require a reasonably modern version of CMake
cmake_minimum_required(VERSION 3.15)
# Name our project
project(TelemetryEngine)
# Specify C++17 as our standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Step 2: Fetching Dependencies
Next, we'll use FetchContent to download GLFW and GLAD. The resource "ImGUI - Modern C++" provides the exact snippets we need.
# Include the FetchContent module to make its commands available
include(FetchContent)
# --- GLFW ---
# Set options to prevent GLFW from building its docs and examples
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
# Declare the GLFW content, specifying its Git repository and a specific version tag
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.3.8 # Use a specific, stable version
)
# This command actually downloads and adds the library to our project
FetchContent_MakeAvailable(glfw)
# --- GLAD ---
# Declare GLAD similarly
FetchContent_Declare(
glad
GIT_REPOSITORY https://github.com/Dav1dde/glad.git
GIT_TAG v0.1.36 # A specific, stable version
)
FetchContent_MakeAvailable(glad)
Step 3: Finding OpenGL and Defining the Executable
OpenGL is part of the system, provided by the graphics driver, so we use find_package to locate it. Then, we define our main executable and its source file.
# OpenGL is a system library, so find_package is appropriate here
find_package(OpenGL REQUIRED)
# Add our executable, which will be built from main.cpp
add_executable(${PROJECT_NAME} main.cpp)
Step 4: Linking Everything Together
Finally, we need to tell CMake to link these libraries to our executable and include their header files in our build.
target_include_directories: Tells the compiler where to find the header files (.h/.hpp) for our dependencies (e.g.,#include <GLFW/glfw3.h>).target_link_libraries: Tells the linker to connect our executable with the compiled code of the libraries.
# Tell our project where to find the header files for GLAD
target_include_directories(${PROJECT_NAME} PUBLIC ${glad_SOURCE_DIR}/include)
# Link our executable against the libraries
# glfw, glad, and OpenGL::GL are the "target" names provided by these libraries
target_link_libraries(
${PROJECT_NAME}
PRIVATE
glfw
glad
OpenGL::GL
)
5. Final Code and Verification
Your complete CMakeLists.txt should look like this:
cmake_minimum_required(VERSION 3.15)
project(TelemetryEngine)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
# --- GLFW ---
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.3.8
)
FetchContent_MakeAvailable(glfw)
# --- GLAD ---
FetchContent_Declare(
glad
GIT_REPOSITORY https://github.com/Dav1dde/glad.git
GIT_TAG v0.1.36
)
FetchContent_MakeAvailable(glad)
find_package(OpenGL REQUIRED)
add_executable(${PROJECT_NAME} main.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC ${glad_SOURCE_DIR}/include)
target_link_libraries(
${PROJECT_NAME}
PRIVATE
glfw
glad
OpenGL::GL
)
To verify that it works, create a simple main.cpp file:
#include <iostream>
// If the build is configured correctly, these includes will be found.
#include <glad/glad.h>
#include <GLFW/glfw3.h>
int main() {
std::cout << "Project configured successfully!" << std::endl;
std::cout << "GLFW and GLAD are linked." << std::endl;
return 0;
}
Now, configure and build your project using your IDE (e.g., VS Code with the CMake Tools extension) or from the command line. The first time you build, CMake will download the dependencies, which might take a minute. If the build succeeds and you can run the executable to see the "Project configured successfully!" message, you have achieved today's goal!
Test your understanding!
Imagine you successfully build the project. Now, you comment out the target_link_libraries(...) block in your CMakeLists.txt and try to build again. What kind of error would you expect to see, and why?
Show answer
You would get a linker error. The compiler would successfully compile main.cpp because target_include_directories is still present, so it can find the header files and knows the functions exist. However, when the linker tries to connect your compiled code with the actual implementations of the GLFW/GLAD functions, it won't find them because you removed the instruction to link against those libraries. The error message would likely contain phrases like "undefined reference to glfwInit" or "unresolved external symbol".
Conclusion
Excellent work! This was a significant and technically dense lesson. You've moved from the abstract world of data serialization to the practical, and often tricky, world of project configuration.
Key Takeaways:
- CMake is a cross-platform meta-build system that generates native build projects from a
CMakeLists.txtscript. - Graphical applications require external libraries for windowing (GLFW) and an OpenGL loader (GLAD).
FetchContentis the modern, robust way to manage dependencies in CMake, making your project self-contained and portable.- The build process involves distinct steps: compiling (needs headers from
target_include_directories) and linking (needs libraries fromtarget_link_libraries).
You now have a solid, empty scaffold for a graphical application. All the necessary dependencies are automatically managed and linked.
What's Next?
With the build system in place, we are ready to write the C++ code that actually uses these libraries. In the next lesson, you will write the boilerplate code to create and manage a native window, initialize a graphics context using GLAD, and implement a basic render loop. This will give us a blank window on the screen—the canvas upon which we will soon paint our ImGui interface.