OpenGL Quick Start

In my previous post, Game Engine Adventures, I talked about why I would even attempt to write my own game engine (for fun!) and how I set up my environment.

In this post I am going to start with the OpenGL equivalent of Hello World, a window with an OpenGL context that displays a simple image.

Package Requirements

I’ve added additional packages to my conanfile: glog (for nice python-like logging for C++), and glfw for easy window creation and input management.

conanfile.py

    requires = [
        "msys2/20210105",
        "cmake/3.20.4",
        "glog/0.5.0",
        "glfw/3.3.4",
    ]

Minimal glfw loop

The glfw documentation claims it “Gives you a window and OpenGL context with just two function calls“. This is a pretty bold claim, and while technically it may be true, it is also a completely non-functional application that creates a window and simply exits.

The real minimal glfw loop looks like this:

main.cpp

#include <glog/logging.h>
#include <GLFW/glfw3.h>

int main(int argc, const char *argv[]) {
    google::InitGoogleLogging(argv[0]);
    FLAGS_logtostderr = 1;
    LOG(INFO) << "Starting";

    if (!glfwInit())
    {
        LOG(ERROR) << "glfwInit::error";
        return -1;
    }

    GLFWwindow* window = glfwCreateWindow(640, 480, argv[0], NULL, NULL);
    if (!window)
    {
        LOG(ERROR) << "glfwCreateWindow::error";
        return -1;
    }

    glfwMakeContextCurrent(window);

    glViewport(0, 0, 640, 480);
    glClearColor(0, 0, 0, 0);

    while (!glfwWindowShouldClose(window))
    {
        // Clear the view
        glClear(GL_COLOR_BUFFER_BIT);

        // Render something
        // TODO...

        // Display output
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();

    LOG(INFO) << "Exiting";
    return 0;
}

This will initialize glog to write to the console, iniatlize glfw and create a window (the “two line” claim?), setup the OpenGL context to render to, begin a loop waiting for the window to close, and cleanup glfw before exiting.

If you run this code it should open a plain black window that can be rendered to. It’s not very sexy.

Measuring Greatness

That’s great to sanity check, but not very interesting. Let’s add a Frames Per Second counter in the title bar. I happen to really like Python fstrings, but I’m not ready to jump into c++ 20 to take advantage of std::format. The good news is that std::format is based on fmtlib, so I’m just going to add that to my conanfile.py

conanfile.py

    requires = [
        "msys2/20210105",
        "cmake/3.20.4",
        "fmt/8.0.0",
        "glog/0.5.0",
        "glfw/3.3.4",
    ]

Now we can update our main render loop to measure how many frames we can render in 1 second. The code here uses glfwGetTime as a more portable clock mechanism, counts how many frames have been rendered, and if the elapsed time is greater than 1 second it updates the titlebar using fmt::format. The obscure “{:>5.0f}” string is fmtlib’s syntax for padding the string with some leading spaces so the fps counter does move all over the place

Did I just pull in a library just to avoid sprintf or something equivalent? Yes and no. Check out the fmtlib benchmarks and you will see it outperforms many libraries, and it has great formatting options that we can take advantage of later.

    double lastTime = glfwGetTime();
    int nbFrames = 0;

    while (!glfwWindowShouldClose(window))
    {
        // Clear the view
        glClear(GL_COLOR_BUFFER_BIT);

        // Render something
        // TODO...

        // Display output
        glfwSwapBuffers(window);
        glfwPollEvents();

        // Calculate FPS
        double currentTime = glfwGetTime();
        nbFrames++;

        if ( currentTime - lastTime >= 1.0 ) {
            glfwSetWindowTitle(window, fmt::format("{:>5.0f} fps", nbFrames/(currentTime - lastTime)).c_str());
            nbFrames = 0;
            lastTime = currentTime;
        }
    }

Old School Cool

Before diving into new OpenGL features like Vertex Buffer Objects, Vertex Shaders, Mesh Shaders, and general rendering pipeline complexity, let’s just sanity check ourselves by replacing the TODO block with a very basic and old school glBegin/glEnd block using glVertex2f immediate mode calls.

        // Draw a triangle
        glBegin(GL_TRIANGLES);
            glColor3f(1.0, 0.0, 0.0);
            glVertex2f(0, 0.5);
            glColor3f(0.0, 1.0, 0.0);
            glVertex2f(-0.5, -0.5);
            glColor3f(0.0, 0.0, 1.0);
            glVertex2f(0.5, -0.5);
        glEnd();

Conclusion

This is very far from a game. It literally outputs a triangle in the least efficient way possible. In my next post I will begin to explore the capabilities of new OpenGL versions and try to benchmark some of the different rendering methods.

Next Post: Modern Rendering Pipelines

mbrandeis