Hello! Welcome back to our project.
In our last lesson, we laid the critical groundwork for our telemetry engine's user interface. You configured a modern CMake project to automatically fetch and link the necessary libraries for windowing (GLFW) and graphics (OpenGL/GLAD). Right now, you have a project that can successfully compile a main.cpp file that includes the GLFW and GLAD headers, but it doesn't do anything visible yet.
Today, we bring that project to life. Our goal is to write the boilerplate code to create and manage a native window. This window will serve as the canvas for our entire visualization suite. We will write the C++ code that tells the operating system to open a window, keep it running, and respond to user input, like clicking the close button.
The Anatomy of a GLFW Application
Before we dive into the code, let's look at the lifecycle of a typical application that uses GLFW. Understanding this sequence is key, as you will write this pattern for almost any native graphical application.
The process follows these distinct steps:
- Initialize GLFW: Start the library.
- Configure Window Hints: Tell GLFW what kind of window and OpenGL context we want (e.g., which version of OpenGL to use).
- Create Window: Create the actual window object and its associated OpenGL context.
- Make Context Current: Tell the operating system that all subsequent OpenGL commands should apply to this new window.
- Load OpenGL Functions: (We'll do this in the next lesson with GLAD).
- Run the Render Loop: A
whileloop that runs every frame, keeping the window open. Inside this loop, we:- Process user input (like closing the window).
- Perform rendering (which will be a blank screen for now).
- Swap the front and back buffers to display the rendered image.
- Terminate GLFW: Clean up all allocated resources before the program exits.

Implementing the Window Boilerplate
We'll now translate those steps into C++ code. The process involves calling a series of functions from the GLFW library in a specific order.
The following video provides a great visual walkthrough of this exact process. We will go through it section by section.
Watch this tutorial by Victor Gordan to see the fundamental steps of creating a window with GLFW. We will break it down piece by piece.
Watch the entire video (around 4 minutes). Pay close attention to the sequence of function calls: glfwInit, glfwWindowHint, glfwCreateWindow, glfwMakeContextCurrent, and the structure of the while loop.
Now, let's break down the code you saw in the video and reconstruct it for our main.cpp.
1. Initialization and Configuration
First, we must initialize the GLFW library. We also need to give it "hints" about the OpenGL context we want to create. For our project, we'll use OpenGL 3.3 and the "Core" profile, which gives us access to modern OpenGL features without legacy functions.
#include <iostream>
#include <GLFW/glfw3.h> // Will be found by CMake
int main() {
// 1. Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}
// 2. Set window hints for OpenGL 3.3 Core Profile
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// ... rest of the code will go here ...
// Last step: Terminate GLFW
glfwTerminate();
return 0;
}
Notice that we immediately write glfwTerminate() at the end. This is good practice to ensure resources are always cleaned up.
2. Creating the Window and Context
Next, we ask GLFW to create the window. This function returns a pointer to a GLFWwindow object, which is an opaque handle to our window. We must check if the pointer is NULL (or nullptr in modern C++), as window creation can fail if the user's system doesn't support the requested OpenGL version.
After successfully creating the window, we must tell GLFW to make its associated OpenGL context the "current" one for this thread. An OpenGL context is essentially a state machine that holds all the data for rendering (shaders, textures, etc.). Any OpenGL calls we make will affect the currently active context.
// Add this after setting window hints
// 3. 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;
}
// 4. Make the window's context current
glfwMakeContextCurrent(window);
3. The Render Loop
If you were to run the code now, a window would flash on the screen and disappear instantly. That's because the main function would finish executing. To keep the window open, we need a loop that runs until we tell it to stop.
This is the render loop.
- The loop condition is
!glfwWindowShouldClose(window). This function checks a flag that GLFW sets totruewhen the user clicks the window's close button. - Inside the loop,
glfwPollEvents()processes any pending events, such as keyboard input, mouse movement, or the close request itself. Without this, the window would be unresponsive. glfwSwapBuffers(window)performs the buffer swap. Modern graphics use a technique called double buffering to prevent flickering. All rendering commands draw to an off-screen "back" buffer. When rendering for the frame is complete,glfwSwapBufferspresents this finished image to the screen by swapping it with the "front" buffer.
// Add this after making the context current
// 5. The Render Loop
while (!glfwWindowShouldClose(window)) {
// --- Input ---
// (We will add input processing here later)
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
// --- Rendering ---
// (We will add rendering commands here later)
// --- Swapping buffers and polling events ---
glfwSwapBuffers(window);
glfwPollEvents();
}
I've added a small check for the Escape key to provide another way to close the window, which is useful for testing.
Putting It All Together
The complete main.cpp file combines all these steps into a functional boilerplate for a graphical application. The learnopengl.com article "Hello Window" provides an excellent textual reference that consolidates everything we've discussed.
To see these concepts explained in a comprehensive text format, please read the 'Hello Window' chapter from learnopengl.com. It covers the exact same lifecycle and provides clear code examples.
Read through the page, focusing on the code snippets and explanations for glfwInit, glfwWindowHint, glfwCreateWindow, glfwMakeContextCurrent, the while loop with glfwPollEvents and glfwSwapBuffers, and finally glfwTerminate. This will solidify your understanding of the code we just built.
Here is the final main.cpp file for you to use in your project. Replace the contents of your current main.cpp with this code, build, and run it. You should see a 1280x720 window with a black background that you can close with the 'X' button or the Escape key.
#include <iostream>
// Make sure GLAD is included before GLFW
// We haven't initialized GLAD yet, but the headers need to be in the right order.
// #include <glad/glad.h>
#include <GLFW/glfw3.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
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Required for macOS
#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;
}
// Make the window's context current
glfwMakeContextCurrent(window);
// The Render Loop
while (!glfwWindowShouldClose(window)) {
// Input
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
// Rendering commands will go here...
// For now, we will clear the screen in the next lesson.
// Swap buffers and poll for IO events
glfwSwapBuffers(window);
glfwPollEvents();
}
// Cleanup
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
Note: I have commented out the GLAD include for now, as we haven't configured it yet. We will uncomment it in the next lesson.
Test your understanding!
What would happen if you moved glfwPollEvents() to be the first line inside the while loop, before glfwSwapBuffers()? Would the application still work correctly? Why or why not?
Show answer
Yes, the application would still work correctly. The order of glfwPollEvents() and glfwSwapBuffers() inside the loop doesn't matter for basic functionality. glfwPollEvents() processes input, and glfwSwapBuffers() displays the rendered frame. Doing one before the other just changes whether you process the current frame's input before or after you show the last frame's image. In more complex applications with high-frequency input, this order can have subtle effects on input latency, but for our purposes, either order is fine. The crucial part is that both are called once per frame.
Conclusion
Congratulations! You've successfully created a native C++ application that opens and manages a window. This is a fundamental skill and a huge step towards building our visualization front-end.
Key Takeaways:
- A graphical application follows a clear lifecycle: Initialize -> Configure -> Create -> Loop -> Terminate.
glfwInit()starts the library andglfwTerminate()cleans it up.glfwCreateWindow()creates the window and an OpenGL context.- The render loop (
while !glfwWindowShouldClose) is the heart of the application, keeping it alive frame by frame. glfwPollEvents()is essential for responsiveness and processing input.glfwSwapBuffers()uses double buffering to present a smooth, flicker-free image.
What's Next?
We have a window and an OpenGL context, but we can't actually use any OpenGL functions yet. Our application doesn't know where to find them in the graphics driver. In the next lesson, you will initialize a graphics context using an API loader (GLAD). This will unlock the full power of OpenGL, allowing us to clear the screen to a specific color and, most importantly, prepare the ground for integrating our first ImGui widgets.