Modern OpenGL: Part 1 Introduction and Setup

Hello and welcome to my new blog. For the first series of posts, we are going to be exploring OpenGL. I’m hoping that the reason that you are here is because you already know what OpenGL is and that you are looking for something a little bit different. If you are not familiar with OpenGL, then it is a Graphics API (Application Programming Interface) that is a specification. All the OpenGL specification states are some functions and the expected behaviour they should invoke based upon a bunch of state. Any piece of hardware or software is allowed to implement OpenGL to result in something that when you call those function, you get an image out the other end. Its pretty old now as well. But don’t let that discourage you!

Why do we need another OpenGL tutorial?

Yes, there are hundreds of OpenGL tutorials out there, why should you consider following this one? Well my first answer to that would be… follow ALL of them. There are some great sites, books, videos and articles out there. I myself learned a lot from them. Why am I making this series then?

  1. I couldn’t find any tutorials that taught OpenGL with the Modern ‘Low Driver Overhead’ Approach. This is the tutorial I would have wanted when I was learning OpenGL. This series differs from some other resources out there in that it focuses on totally modern OpenGL and skips to the most performant style and skips some legacy stuff.
  2. There is no harm in there being another resource out there for people to learn from. I myself learn best when there are multiple resources that all explains things slightly differently that I can cross reference and fill in any gaps in my understanding from one resource or another. I have my own way of explaining things. The way I explain things may be slightly different to someone else and that might help something click in someone’s mind. I am inspired by other content creators (I can’t go without mentioning @JoeyDeVriez from and an Yan ‘@TheCherno‘ Chernikov from The Cherno youtube channel), so I want to spread my own insights as well in the hope it might inspire others.
  3. The Cherno’s lessons are quite comprehensive and are great at showing of how various parts of opengl work. I wanted my course to be quite linear in structure so someone can follow in one straight path. Also I wanted to keep it as simple as possible and not have too much abstraction until quite further down the line, and even then I am thinking of a separate tutorial series for game engine architecture which would deal with a lot of the abstraction.
  4. This is also a way to ensure that I myself understand the material.

If I can’t explain it, then I don’t contain it!

Daniel ‘dokipen’ Elliott Jones

Main Goals

  • Simple and Clean Code – This means readable code with clear names and not introducing complexity for complexities sake.
  • Incremental Learning – We only just enough to get the point across and then once we have fully understood a topic, we move on.
  • Have fun – other wise what’s the point of us being on this ball in space?

The Tools we will use

  • C++ 11 using modern best practices, which means RAII, lambdas, standard library data structures and algorithms etc… (c++11, 14, 17 and now 20 are fine, its just that we wont really be using any features which distract us)
  • vcpkg to manage our dependencies (no-one likes spending hours building libraries and dealing with linclude directories and linking errors)
  • cmake to configure our project
  • Visual studio for the c++ compiler (well we don’t have to, but it keeps setup simple).
  • QtCreator as an IDE (I use this at work and is a pretty good IDE with debugging interface and code navigation)
  • GLFW3 as out windowing library
  • glm as our math types and operations library
  • glbinding for getting our OpenGL extensions. This is a modern alternative to GLEW and even has some awesome error checking helping functionality.
  • fmt lib as out text formatting and printing library. This library is already being standardized into c++20 so we should get used to using it. Think of it as a type-safe printf on steroids or c++’s answer to python’s string formatting.

What is ‘modern’ OpenGL?

There are two main goals for making OpenGL fast.

  1. Reducing Draw Calls – These are the functions you invoke to tell OpenGL to render something. Every time you do one of these, there is a cost. We will be looking at techniques that allow us to render lots of things with less draw calls.
  2. Reduce Binding – Binding is the action that you have to perform to make something ‘current’ or ‘active’ before you use them. For instance if you want to upload vertices, you have to ‘bind’ a vertex buffer to a particular binding point, before you can upload data to it or change parameters on it. ‘Binding’ is the way that OpenGL historically was designed due to it being a ‘State Machine’, where all operations depended on OpenGL being in a valid state. You would typically have to bind an object (like buffers and textures) to make them the ‘current’ object. Then any subsequent operations would apply to that object.

After watching some youtube videos from recent years, this is what ‘modern’ OpenGL means to me….

  1. ‘Direct State Access’ (DSA) functions
  2. Multi-Draw Indirect
  3. Texture Arrays (and Bindless Textures)
  4. Manual Synchronization of Data Upload

There are other aspects of course, but these stand out as being the top things that help performance. What are they I hear you ask? The four items listed above are features designed to reduce time spent in the OpenGL driver on two things, ‘Binding’ and ‘Validation’ (the checks behind the scenes that the driver would perform that would ensure that everything was ‘correct’ and that no errors would occur due to the user calling an operation while the wrong thing was bound.)

So what are those 4 things I mentioned?

Direct State Access

This is how we can avoid a lot of the issues around binding.

Like we have said previously, we would have to bind an object to be able to perform some operation (for example we would have to bind a buffer to be able to upload data to it, bind a texture to upload texture data or bind a shader program to be able to send uniform data to it). With Direct State Access, we can now perform operations directly on objects by using and id/name. Want to upload data to a buffer? Just do it!

It not only reduces the amount of binding you have to do, it also potentially reduces errors because it is harder to forget to bind something before. It can’t remove the need for binding, but can minimize it a lot.

Multi-Draw Indirect

Multi-Draw Indirect is a feature that allows users to bunch up a load of Draw Calls (a function you invoke to cause OpenGL to do some drawing) and submit them all at once. This can reduce the need for the driver to perform validation after each individual draw call. Even better, these draw calls can be built in parallel on the CPU to make the most of your cores.

Texture Arrays

A Texture Array is bascially like a standard texture except that is contains many ‘layers’ stacked on top of each other. Each layer can store a whole texture and it can be addressed by its layer index. This again can reduce the need to bind multiple things.

The only downside to Texture Arrays is that one texture array can only contains textures that are all the same size/format. That means that if you want to have textures of different sizes and formats in your program, then you will have to have a different texture array for each size and format combination. The limitation of OpenGL being able to bind only usually 80 minimum textures at once (which is why, as you’ll see, we use texture arrays to get around this limitation) means that we can have 80 texture arrays bound once and never have to rebind. Hopefully you can design your content pipeline around this constraint which will make life a lot easier on the OpenGL side. For example, we could support 512, 1024, 2048 and 4096 textures with the RGBA format which would take up 4 slots.

Manual Synchronization

The GPU is a very complex processor and doesn’t actually work in lockstep with your CPU. When you submit work to the GPU, it goes off and does work completely separately from the CPU with no guarantee as to when it will complete. You can choose to do simple waiting for the GPU to finish, but this effectively blocks the CPU and make it sit idle when it could be doing useful work. Adding in some code to indicate when the CPU and GPU should synchronize with each other can improve performance a lot, especially when you are uploading data to the CPU often.

Hardware Support for OpenGL 4.5+

Support for OpenGL 4.6 on hardware is okay. You may still have a graphics card or integrated graphics that does not support the full OpenGL 4.6 core feature set. I found myself recently trying to code against some older cards and had varying success. I was trying out a early 2013 Macbook Pro in BootCamp and found that the Nvidia 650m had full OpenGL 4.5+. But the Late 2014 Mac Mini only has 4.3. Also OSX is deprecating opengl and only has support for 4.1.

Luckily even some of the hardware that supports OpenGL 4.3 has what are known as ‘Extensions’. These are features of OpenGLthat are implemented semi-officially with the intention that they will become part of a later core version of OpenGL. So while 4.3 might be the core supported version, there might still be extensions supported by the driver for the above features. Bindless Textures isn’t actually core (and is one reason why I don’t fully endorse using them), but using them can be a great performance win and does have wide support on most recent graphics cards.

In this series, I will actually be targeting 4.5. This is because I am able to do some development on my macbook with doesn’t normally support opengl versions above 4.1. But with an ubuntu virtual machine with a piece of software called llvmpipe which is a software implementation as part of the mesa project, I am able to test my code anywhere I go. It is also a conformant implementation and gives great debug messages. In the future, I am looking forward using another piece of software called zink (also a mesa project) to run opengl 4.6 on top of vulkan on moltenvk on metal on mac.

Project setup

First we should install these….

  • Visual Studio 2019 Community Edition, along with its C++ Desktop workload
  • QtCreator
  • Cmake 3.18+
  • vcpkg

Visual Studio

I’m going to be installing visual studio to be able to use it’s compiler. It is possible to download the build tools separately, but I’ve had trouble with that before so installing the full thing seems to work good for me. Plus if you ever need to go in and use its editor, then you can. But I’m going to be using a different IDE in this series. Feel free to use whatever IDE you feel comfortable with. I myself like to use Visual Studio Code from time to time as well.

QtCreator as our IDE

I’m choosing to use QtCreator as the IDE. It has the option to open an existing project which just consists of a folder of source files and a CMakeLists.txt (yes I know Visual Studio can do that too but it’s not been completely reliable all the time for me).


Dependencies with vcpkg

I’ve chosen to use vcpkg for this project as it is simple enough that using the libraries from vcpkg will be enough. If this was a more serious project and I needed more control over the build setting, then I would probably build them manually. But this is so convenient and hopefully should be the same for if you are following this. I like to minimize c++ build shenanigans as much as possible. I’ve spent too many hours into the night trying to build boost and whatever other libraries to know that if you don’t have to then just dont.

I won’t tell you here how to install vcpkg, you can find that out easily from the github page. I’ll just tell you which dependencies we will need for now. As time goes on, we might add to this list.

./vcpkg.exe install glfw3:x64-windows # for windowing
./vcpkg.exe install glbinding:x64-windows # for OpenGL extensions and easy error handling
./vcpkg.exe install stb:x64-windows # for texture loading
./vcpkg.exe install fmt:x64-windows # for c++20 style printing to the console

These are the step you can take to get a project up and running…..

  • Create a chapter1_HelloWorld.cpp file. This is going to contain our main function (the entry point into the program.
#include <fmt/core.h>  // for fmtprint() implements c++20 std::format

int main() {
    fmt::print("This is going to be fun!\n");

Store it in a src folder in your project directory and at that same level, create a CMakeLists.txt file which is going to configure our project for compiling and linking.

  • In the CMakeLists.txt file, add this content to set up the project. I won’t go into too many details now. Hopefully its a simple enough script to be self explanatory.
# The name of our project

# we want to use a recent version of cmake
cmake_minimum_required(VERSION 3.18)

# makes sure we have dependencies on our machine. sets variables for us to be able to pass to the linker
find_package(OpenGL REQUIRED)
find_package(glbinding CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(glm REQUIRED)
find_package(fmt CONFIG REQUIRED)

# takes the files in the src directory and adds them to a variable called SRC_LIST
aux_source_directory(src/ SRC_LIST)

# tells cmake that we are making an exectutable program 
# whos source is from the files in the SRC_LIST variable
add_executable(chapter1 src/chapter1_HelloWorld.cpp)

# tells the compiler to use c++ 11 

# create a variable which will be the libraries that we want the c++ compiler to link to
set(LIBRARIES fmt::fmt

# which libraries our program must link against using the variable we previously set
target_link_libraries(chapter1 PRIVATE ${LIBRARIES})
  • Open QtCreator and go to the Tools -> Options menu item. Under the Kits section, you should see the MSVC compiler detected. I like to make the 64-bit one default. If you installed msys/mingw as part of the full Qt installer, then that might work for you as well.
  • Then choose to ‘Open’ a project.

Navigate to the directory where your CMakeLists.txt file is

  • To configure the project (which will invoke cmake for us) we should choose the Kit that we want to use. This also allows us to choose where we want the build files to go.
  • We also need to tell CMake where to get the libraries that we need to compile and link against. We can do that by passing it our vcpkg tool chain file. (If you have forgotten where that is, just go to the vcpkg folder, open a cmd/powershell terminal and type vcpkg integrate install and it should print the path for you. Paste that path into the ‘Initial CMake parameters window (which is found in the Build section).

CMake should be invoked now and you should see in the General Messages window that the generating is done and now you can hit Ctrl + R to run the program. You will then get a printout in the Application Output tab….

Now we have everything up and running then please continue onto Part 2, where we will be creating a window!