How to setup an SDL2+OpenGL project with CMake

25.05.19

The SDL2 library can be used to create and manage an OpenGL environment, which then can be accessed directly using the OpenGL API. This is a quite comfortable alternative to the "common" ecosystem of add-ons and extension wranglers. But how exactly to setup the build, i.e. what headers to include and what libraries to link to, is quite hard to find out.

We want to use CMake for our build process of our C or C++ project. For loading the necessary information about our dependencies (SDL2 and OpenGL) we use the find_package command.

find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)

These commands will introduce variables of the form SDL2_* / OPENGL_* into the scope our build script. We will use them to add items to our build process. But first we have to add our executable to CMake:

add_executable(prog main.c)

This command declares our intention to build an executable named prog consisting of the single source file main.c. This is a type of build target. Now we can use various target_* commands to modify it:

target_include_directories(prog PUBLIC ${SDL2_INCLUDE_DIRS})
target_include_directories(prog PUBLIC ${OPENGL_INCLUDE_DIR})

First we add the include directories to our target prog using our variables we received from our invocations of find_package. Do note that the include directory variable for OpenGL for some reason is called OPENGL_INCLUDE_DIR without an S at the end. This completely diverges from how other find_package calls behave and has cost me a significant amount of time to figure out. The cryptic errors and silent failures of CMake did not help in figuring that one out.

A very helpful command for debugging CMake is message(STATUS ...). It allows you to print arbitrary strings at configuration time. This way you can check if a variable has sensible contents for example:

message(STATUS "<<${OPENGL_INCLUDE_DIRS}>>")
# This will print <<>> because OPENGL_INCLUDE_DIRS is empty.

Once everything works you can remove or comment out the message commands of course.

After that we add the libraries we want to link to:

target_link_libraries(prog ${SDL2_LIBRARIES})
target_link_libraries(prog ${OPENGL_LIBRARIES})

Now CMake knows how to build our executable prog.

So we move on to our source file main.c. We have to include the correct headers to use OpenGL commands in our C (or C++) program. This is as easy as putting the following at the top of our code:

#define GL_GLEXT_PROTOTYPES
#include <SDL.h>
#include <SDL_opengl.h>

We do not include the normal OpenGL header like you will find in most OpenGL examples. Instead we use the header SDL_opengl.h which behaves like the normal one, except it will make the OpenGL API use the context managed by SDL2. The definition of GL_GLEXT_PROTOTYPES before the inclusion of the SDL_opengl.h header is important to enable most of the modern OpenGL API.

Inside our main function we first create an SDL2 window which has the SDL_WINDOW_OPENGL flag set. Then we set some attributes which define our OpenGL context. Finally we create this context. It will be automatically be the current context for the OpenGL API.

SDL_Init(SDL_INIT_VIDEO);

SDL_Window *window = SDL_CreateWindow("OpenGL Test",
  SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 500, 500, SDL_WINDOW_OPENGL);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

SDL_GLContext context = SDL_GL_CreateContext(window);

From there we can use the OpenGL functions like in any example code (like in this nice tutorial). They will behave like with any other OpenGL context and the rendering will happen inside the SDL2 window. We can then combine this seamlessly with any other component of SDL2. For example we can use SDL2's nice input / event handling to create an interactive OpenGL application. This way we have achieved our goal of using SDL2 and OpenGL together inside a program built with CMake.