Brain Dump

OpenGL

Tags
opengl

An API for cross platform graphical rendering across multiple available hardware and software environments. The OpenGL system is implemented as a large state machine, with API calls modifying the state (such as pushing data onto the GPU where rendering calculations are much quicker than the CPU).

Initialise OpenGL guide

Before we can get to drawing with opengl we need a window to draw in. This is generally a platform specific problem and with OpenGL actively trying to be generic we have to do this ourselves. There're a few libraries which can automate this process for us, I'll be using glfw.

There's also the need to setup the appropriate drivers for your graphics card, we'll be using the GLAD library to do this for us.

Setup Project

Steps:

  1. Download and install the precompiled GLFW binaries for your current system.
  2. Go to the GLAD online portal and select the OpenGL version you want to develop with. Download glad.zip and extract it to your project directory.
  3. Move src/glad.c to ./glad.cc (or adapt the Makefile below as required).

  4. Create a Makefile at the root of your repository with the following contents.

.POSIX:

CC = g++
CCFLAGS = -lglfw -lGL -lX11 -lpthread -lXrandr -lXi -ldl -I include
SRC = glad.cc main.cc
OBJ = $(SRC:%.cc=%.o)

%.o: %.cc
	$(CC) $(CCFLAGS) -c $^

main: $(OBJ)
	$(CC) $(CCFLAGS) -o $@ $^

clean:
	rm -f main $(OBJ)

run: main
	./main

At this point, it's worth double checking that you can compile and run your project. Create a simple program that just outputs hello world at main.cc.

#include <stdio.h>

int main() {
    printf("hello world\n");
    return 0;
}

And then run make run to see whether everythings setup correctly.

Creating a Window

Now we can go on to make a window. First lets include the two libraries we'll need.

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

Then in main we're gonna need to let GLFW know some information about our environment.

glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

Then we're going to try to create a window, or exit straight away if that fails.

GLFWwindow* window = glfwCreateWindow(800, 600, "Window Title", NULL, NULL);
if (window == NULL) {
    printf("Failed to open window\n");
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

And to make sure the window doesn't close immeadiately after we've created it, we should setup a render loop that waits until the close flag is triggered on the window.

while (!glfwWindowShouldClose(window)) {
    // handle input

    glfwSwapBuffers(window);

    // start rendering
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // set state
    glClear(GL_COLOR_BUFFER_BIT); // clear screen

    // update environment
    glfwPollEvents(); // check if somethings happened
}

Now the only way to close the window is using the close button on your desktop environments window decoration. If you're using a window manager... I'm sure you can figure it out.

To finish we should clean up any resources before exiting.

glfwTerminate();
return 0;

Setting up OpenGL

Having a window is all well and good, but we should prepare it for drawing. Firstly we should initialise GLAD using the following snippet. don't ask me what it does.

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    printf("Failed to initialize GLAD\n");
    return -1;
}

Then we need to tell OpenGL the size of our viewport so that it knows the region it can draw in. Technically you can setup multiple OpenGL drawing regions in a single window, but for now let's just register one with the same size as our window.

glViewport(0, 0, 800, 600);

Detecting Changes

GLFW lets us bind handlers for different events. The event we should be focused the most on is resize, we should alter our OpenGL viewport whenever our windows dimensions have changed.

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

Then in our main we can assign the handlers before we start polling for events.

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

Lets also check pressed keys so we don't have to manually exit the window with our mouse.

void process_input(GLFWwindow *window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS ||
	glfwGetKey(window, GLFW_KEY_Q)      == GLFW_PRESS) {
	glfwSetWindowShouldClose(window, true);
    }
}

And then update our render loop to be:

while(!glfwWindowShouldClose(window)) {
    process_input(window);

    // rendering goes here

    glfwSwapBuffers(window);
    glfwPollEvents();
}

Links to this note