Modern OpenGL: Part 2 Window Creation

Now we get to the fun part!

This chapter will see us…

  • create a window in our operating system,
  • start a render loop
  • clear the background colour

Headers

Lets start with the headers that we need to include

#include <chrono> // current time
#include <cmath> // sin & cos
#include <array>
#include <cstdlib>    // for std::exit()

#include <fmt/core.h> // for fmt::print(). implements c++20 std::format

// this is really important to make sure that glbindings does not clash with
// glfw's opengl includes. otherwise we get ambigous overloads.
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include <glbinding/gl/gl.h>
#include <glbinding/glbinding.h>

We start with some standard library headers. We use…

  • <chrono> to be able to query the current time
  • <cmath> so we can feed that time into the sin and cos functions to get some animated values.
  • <array> to store some colour values and
  • <cstdlib> for exiting the program if our window isn’t initialized correctly

Then more interestingly we will use…

  • <fmt/core.h>

…to be able to print formatted text to the console. This deviates from using c++ iostreams and std::cout to output from our program. The fmt library is being standardized into c++ and part of it is already in c++20. We use the library here which is c++11 compatible and has the fmt::print() function which we will use extensively.

Then we need a library that will actually open a window for us. We are using GLFW so we will include…

  • <GLFW/glfw3.h>

the header along with a special #define GLFW_INCLUDE_NONE which will allow us to combine glfw with the next library…

  • <glbinding/gl/gl.h>
  • <glbinding/glbinding.h>

which gives us access to all of opengl’s functions (there are ways to do this manually but this library makes it really easy).

using namespace gl;
using namespace std::chrono;

Here we use some ‘using’ aliases to make our code a bit more readable and to also reduce the need to put gl:: in front of all of the OpenGL calls. glbinding actually exposes the opengl functions within it’s own namespace, so this effectively make our program look like its calling opengl functions directly.

int main() {

    auto startTime = system_clock::now();

So here we are in the entry point to our program. We start off by capturing the current time using std::chrono. This will allow us to do animation.

    const auto windowPtr = []() {
        if (!glfwInit()) {
            fmt::print("glfw didnt initialize!\n");
            std::exit(EXIT_FAILURE);
        }

Next we start the initialization process. This is where we will be getting our program ready to be able to use OpenGL. First is calling the glfwInit() function to tell glfw to get ready to do its thing. If glfw failed to initialize then we exit the program. Rather than rip out this code into a separate function, I’ve put this section into a c++ immediately invoked lambda. This is pretty much like any other function, just that its declared ‘in place’. There is no reason to do this in this case as its code that it only runs once and could have been written without it but it indents the code and its a feature that we will use in future chapters.

      
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);

Here we are setting a hint to glfw that we will want it to create an OpenGL context that is a particular version. The context is the ‘instance’ of opengl that we send commands to. We want the latest and greatest so lets ask for 4.6

        auto windowPtr = glfwCreateWindow(
            1280, 720, "Chapter 1 - Window Creation", nullptr, nullptr);

Now we actually ask glfw to create our window! We give it the dimensions that we want our window to have as well as a title that will be displays in the title bar. It returns a raw pointer to a GLFWWindow.

        if (!windowPtr) {
            fmt::print("window doesn't exist\n");
            glfwTerminate();
            std::exit(EXIT_FAILURE);
        }

If the pointer returned was null, then glfw had trouble creating the window and we terminate the glfw library and exit the program.

        glfwSetWindowPos(windowPtr, 520, 180);
        glfwMakeContextCurrent(windowPtr);
        glbinding::initialize(glfwGetProcAddress, false);
        return windowPtr;
    }();

Then we instruct glfw to make our window be associated with the current opengl context.

Then we call on the glbindings library to initialize. This will use the glfwGetProcAddress function to create for us all of the opengl functions that the default gl header doesn’t expose. This has to reach into some deep guts of opengl and if we did all of that boilerplate manually, then it would look pretty ugly, so calling this one function really helps us out here. Also it can do this lazily so that it only does this when we call the functions on demand (thats what the false parameter does. If we wanted glbindings to do this work eagerly up front, we could call it with true).

    std::array<GLfloat, 4> clearColour;

We will want to clear the background of our window so we create a std::array of 4 floats which are the Red, Green, Blue and Alpha values that we want to clear. We don’t initialize it yet as we will be filling it our with live values inside our render loop.

    while (!glfwWindowShouldClose(windowPtr)) {

        auto currentTime =
            duration<float>(system_clock::now() - startTime).count();

        clearColour = {std::sin(currentTime) * 0.5f + 0.5f,
                       std::cos(currentTime) * 0.5f + 0.5f, 0.2f, 1.0f};

        glClearBufferfv(GL_COLOR, 0, clearColour.data());
        glfwSwapBuffers(windowPtr);
        glfwPollEvents();
    }

Here we are in our render loop. In each loop iteration we check for the condition of wether the window should close. glfw can tell us this based on wether the you or the user clicked on the close window button, or maybe the os closed the window for some reason.

Then we calculate the current time since the program started. We do that by taking the difference between the current time (which is measured from 1970 or something like that( and the time captured at the beginning of the program. Using that time, we set the value of the clear colour so that the colours sweep though various values as the sine and cosine waves propogate though time.

Then we actually clear the colour buffer using that colour we have just set.

Once the buffer is clear, we then tell glfw to swap the back buffer to show the image that we have just drawn with OpenGL

    glfwTerminate();
}

And finally we terminate glfw so it can wrap itself up. We now have our program displaying some pretty colours!

Leave a Comment

Your email address will not be published. Required fields are marked *