Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 18

GLFW Window Management

Recommended resources

Introduction

Welcome back! In our last lesson, we established the high-level architecture of OpenGL. We learned that it's a specification implemented by graphics drivers, and that our application acts as a "client" sending commands to the GPU "server". The server's state is encapsulated in an object called the OpenGL context, which is essentially a large state machine.

Today, we take our first practical step in building our game engine. Before we can send any rendering commands, we need a window to draw in and, crucially, an OpenGL context to send commands to. This lesson directly addresses that need. Our learning outcome is to use the GLFW library to create and manage a window and its context.

We will cover the entire lifecycle of a window in a C application, from initialization to termination, using a small, powerful library called GLFW.


Why Use a Library for Windowing?

Creating and managing a window is an operating system-specific task. The code to create a window on Windows (using the Win32 API) is completely different from the code for macOS (Cocoa) or Linux (X11/Wayland). Writing and maintaining code for each platform would be a significant distraction from our main goal of learning 3D graphics.

This is where GLFW comes in. GLFW is a lightweight, open-source, cross-platform library written in C. Its primary purpose is to provide a simple API for creating windows, managing OpenGL contexts, and handling user input. By using GLFW, we can write a single piece of code that will work on Windows, macOS, and Linux, allowing us to focus on what matters: the graphics.


The GLFW Workflow: From Init to Terminate

Getting a window to appear on screen involves a clear sequence of steps. We will walk through each one, culminating in a complete C program that you can run.

We will primarily follow the process shown in the video below. While the video uses C++, the functions and the overall structure are identical to what we will use in C.

OpenGL Tutorial 1 - Window

This video from Victor Gordan provides a concise and clear demonstration of the entire process of creating a window with GLFW. Watch it to get a complete overview of the functions we'll be discussing.

Watch the video from the beginning to the end (00:00 - 04:24). Pay attention to the sequence of function calls: initializing GLFW, setting hints, creating the window, making the context current, and running the main loop.

Now, let's break down the steps from the video with more detail.

1. Initialization and Cleanup

Before any GLFW functions can be used, the library itself must be initialized. This is done with a single call.

  • glfwInit(): This function initializes the GLFW library. It should be the very first GLFW function you call. It returns GL_TRUE (which is just 1 in C) on success and GL_FALSE (0) on failure. You should always check the return value.

Equally important is cleaning up when your application is finished.

  • glfwTerminate(): This function frees any allocated resources, destroys any remaining windows, and restores the system state. It should be the last GLFW function you call before your program exits.

A robust program structure always pairs initialization with termination:




#include <stdio.h>
#include <GLFW/glfw3.h>

int main(void) {
    if (!glfwInit()) {
        printf("Failed to initialize GLFW\n");
        return -1;
    }

    // ... create window and run main loop ...

    glfwTerminate();
    return 0;
}

2. Configuring the Window with Hints

Before creating the window, we can give GLFW "hints" about the properties we want the window and its OpenGL context to have. This is how we request a specific version of OpenGL.

  • glfwWindowHint(int hint, int value): This function sets various options. The two most important hints for us are:
    • GLFW_CONTEXT_VERSION_MAJOR and GLFW_CONTEXT_VERSION_MINOR: We'll set these to 3 and 3 respectively to request OpenGL 3.3.
    • GLFW_OPENGL_PROFILE: We'll set this to GLFW_OPENGL_CORE_PROFILE to get access to modern OpenGL functions without the clutter of old, deprecated features.
// Place these calls after glfwInit() but before glfwCreateWindow()
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

3. Creating the Window and Context

With the hints set, we can now ask GLFW to create the window. This single function also creates the associated OpenGL context for us.

  • glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share):
    • width, height, title: The dimensions and title of the window.
    • monitor: Used for creating a fullscreen window (we'll use NULL for windowed mode).
    • share: Used to share resources between windows (we'll use NULL).
    • The function returns a pointer to a GLFWwindow object, or NULL if creation failed. This pointer is our handle to the window.

You must always check if the window creation was successful.

GLFWwindow* window = glfwCreateWindow(800, 600, "3D Game Engine", NULL, NULL);
if (window == NULL) {
    printf("Failed to create GLFW window\n");
    glfwTerminate();
    return -1;
}

4. Making the Context Current

We now have a window and an OpenGL context. As we discussed in the last lesson, the context is the state machine on the GPU. But our program needs to tell the operating system which context our OpenGL commands should be sent to.

  • glfwMakeContextCurrent(GLFWwindow* window): This function tells GLFW to make the context of the specified window the current one on the calling thread.

This is a critical step. No OpenGL calls will work until you have a current context.

GLFW Window Creation and Context Management in C++
This image shows the C++ equivalent of our code. The tooltip for `glfwMakeContextCurrent` perfectly explains its purpose: it attaches the OpenGL context of our window to the current thread, enabling us to send rendering commands.

After creating the window, we immediately make its context current:

glfwMakeContextCurrent(window);

5. The Render Loop

If our main function were to end here, the program would exit and the window would disappear instantly. We need a loop that keeps the program running, processes user input, and draws to the screen, until the user decides to close the window. This is commonly called the render loop or game loop.

while (!glfwWindowShouldClose(window)) {
    // --- Game logic and rendering will go here ---

    // Swap buffers to display the new frame
    glfwSwapBuffers(window);

    // Process events like keyboard input or window close requests
    glfwPollEvents();
}

Let's break down the three key functions here:

  • glfwWindowShouldClose(window): This function returns true if the user has attempted to close the window (e.g., by clicking the 'X' button). Our loop continues as long as this is false.
  • glfwPollEvents(): This function processes any pending events (like mouse movement, key presses, or the window close event) and calls any registered callback functions. Without this, the window would become unresponsive.
  • glfwSwapBuffers(window): This function is essential for smooth rendering. To avoid flickering, applications use a technique called double buffering. All rendering commands draw to a hidden back buffer. When drawing is complete for a frame, glfwSwapBuffers swaps the back buffer with the visible front buffer, displaying the newly rendered image all at once.

To learn more about these core loop components, you can refer to the official documentation.

GLFW and LearnOpenGL Documentation

The official GLFW 'Getting started' guide and the 'Hello Window' chapter from LearnOpenGL provide excellent written explanations for the render loop and its components.

From the GLFW 'Getting started' guide, read the sections 'Checking the window close flag', 'Swapping buffers', and 'Processing events'. This will give you the official definitions for the functions that make up our main loop.


A Complete C Example

Let's put all the pieces together into a single, minimal C program. This program will initialize GLFW, create an 800x600 window, and run a loop until the window is closed. For now, the window will just be black, but it's a monumental first step!

// main.c



#include <stdio.h>
#include <GLFW/glfw3.h>

int main(void) {
    // 1. Initialize GLFW
    if (!glfwInit()) {
        printf("Failed to initialize GLFW\n");
        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);

    // 3. Create the window
    GLFWwindow* window = glfwCreateWindow(800, 600, "My First Window", NULL, NULL);
    if (window == NULL) {
        printf("Failed to create GLFW window\n");
        glfwTerminate();
        return -1;
    }

    // 4. Make the window's context current
    glfwMakeContextCurrent(window);

    // 5. The Render Loop
    while (!glfwWindowShouldClose(window)) {
        // Our rendering code will go here in the future
        
        // Swap front and back buffers
        glfwSwapBuffers(window);

        // Poll for and process events
        glfwPollEvents();
    }

    // 6. Clean up and terminate
    glfwDestroyWindow(window); // Optional here, as glfwTerminate does this
    glfwTerminate();
    
    return 0;
}

A Note on Compiling: To compile this code, your compiler needs to know where to find the GLFW header files (-I flag) and which library file to link against (-l flag). This process is covered in the course outcome on build systems. For now, the focus is on understanding the C code itself.


Conclusion

Congratulations! You now understand the complete process of creating and managing a window and an OpenGL context using GLFW. This is the foundational piece of code for any graphics application we will build.

Key Takeaways:

  • GLFW is a C library that abstracts away OS-specific details for windowing, context creation, and input.
  • The lifecycle of a GLFW application follows a strict order: glfwInit, set hints with glfwWindowHint, glfwCreateWindow, glfwMakeContextCurrent.
  • The glfwMakeContextCurrent function is the bridge that connects our application thread to the OpenGL state machine (the context).
  • A while loop using glfwWindowShouldClose keeps the application alive.
  • Inside the loop, glfwPollEvents keeps the window responsive, and glfwSwapBuffers displays the rendered frame using double buffering.
  • Finally, glfwTerminate must be called to release all resources.

Preview of the Next Lesson:

We have a window and a context, but we still can't call any modern OpenGL functions like glGenBuffers. Why? Because the locations of these functions in the graphics driver are not known at compile time. In the next lesson, we will use the GLAD library to load pointers to OpenGL functions at runtime, finally unlocking the full power of the graphics API.